UPDATE
This commit is contained in:
@@ -3,6 +3,164 @@ Static mock data for Yield & Harvest Prediction API.
|
||||
Mirrors the yieldPredictionChart and harvestPredictionCard dashboard card shapes.
|
||||
"""
|
||||
|
||||
CONFIG_SLIDERS_ONLY = {
|
||||
"sliders": [
|
||||
{
|
||||
"key": "light",
|
||||
"label": "نور",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 5,
|
||||
"unit_type": "percent",
|
||||
"default_value": 75,
|
||||
"icon": "☀️",
|
||||
},
|
||||
{
|
||||
"key": "water",
|
||||
"label": "آب",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 5,
|
||||
"unit_type": "percent",
|
||||
"default_value": 65,
|
||||
"icon": "💧",
|
||||
},
|
||||
{
|
||||
"key": "soil_ph",
|
||||
"label": "pH خاک",
|
||||
"min": 4,
|
||||
"max": 9,
|
||||
"step": 0.5,
|
||||
"unit_type": "number",
|
||||
"unit": "",
|
||||
"default_value": 6.5,
|
||||
},
|
||||
{
|
||||
"key": "growth_speed",
|
||||
"label": "سرعت رشد",
|
||||
"min": 0.5,
|
||||
"max": 5,
|
||||
"step": 0.5,
|
||||
"unit_type": "number",
|
||||
"unit": "x",
|
||||
"default_value": 1.5,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
CONSTANTS = {
|
||||
"max_height": 280,
|
||||
"max_leaves": 14,
|
||||
"max_branches": 6,
|
||||
"max_yield": 500,
|
||||
"yield_unit": "g",
|
||||
"yield_rate_unit": "g/s",
|
||||
"height_unit": "px",
|
||||
}
|
||||
|
||||
CHART_CONFIG = {
|
||||
"title": "پیشرفت رشد",
|
||||
"x_axis_label": "زمان (ثانیه)",
|
||||
"series": [
|
||||
{
|
||||
"key": "height",
|
||||
"label": "ارتفاع (px)",
|
||||
"y_axis_id": "yHeight",
|
||||
"min": 0,
|
||||
"max": 280,
|
||||
"unit": "px",
|
||||
},
|
||||
{
|
||||
"key": "leaves",
|
||||
"label": "تعداد برگ",
|
||||
"y_axis_id": "yLeaf",
|
||||
"min": 0,
|
||||
"max": 14,
|
||||
},
|
||||
{
|
||||
"key": "yield",
|
||||
"label": "محصول (g)",
|
||||
"y_axis_id": "yYield",
|
||||
"min": 0,
|
||||
"max": 500,
|
||||
"unit": "g",
|
||||
},
|
||||
{
|
||||
"key": "yield_rate",
|
||||
"label": "نرخ محصول (g/s)",
|
||||
"y_axis_id": "yYieldRate",
|
||||
"min": 0,
|
||||
"unit": "g/s",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
_labels = [f"{i * 0.2:.1f}s" for i in range(51)]
|
||||
_height = [round(142 * (i / 50) ** 0.9) for i in range(51)]
|
||||
_leaf = [min(5, int(i / 10)) for i in range(51)]
|
||||
_yield = [round(12.4 * (i / 50) ** 1.2, 1) for i in range(51)]
|
||||
_yield_rate = [round(0.087 * max(0, (i - 15) / 35), 3) for i in range(51)]
|
||||
|
||||
START_RESPONSE_DATA = {
|
||||
"constants": CONSTANTS,
|
||||
"chart": CHART_CONFIG,
|
||||
"plant": {
|
||||
"height": 142,
|
||||
"leaves_count": 5,
|
||||
"branches_count": 2,
|
||||
"fruits_count": 0,
|
||||
"yield": 12.4,
|
||||
"yield_rate": 0.087,
|
||||
"tick": 520,
|
||||
"is_healthy": True,
|
||||
"can_continue": True,
|
||||
},
|
||||
"progress": {
|
||||
"growth_progress": 50,
|
||||
"light_status": 75,
|
||||
"water_status": 65,
|
||||
"yield_progress": 2.5,
|
||||
"yield_current": 12.4,
|
||||
"yield_rate_current": 0.087,
|
||||
},
|
||||
"chart_history": {
|
||||
"labels": _labels,
|
||||
"height_history": _height,
|
||||
"leaf_history": _leaf,
|
||||
"yield_history": _yield,
|
||||
"yield_rate_history": _yield_rate,
|
||||
},
|
||||
}
|
||||
|
||||
STATE_RESPONSE_DATA = {
|
||||
"plant": {
|
||||
"height": 142,
|
||||
"leaves_count": 5,
|
||||
"branches_count": 2,
|
||||
"fruits_count": 0,
|
||||
"yield": 12.4,
|
||||
"yield_rate": 0.087,
|
||||
"tick": 520,
|
||||
"is_healthy": True,
|
||||
"can_continue": True,
|
||||
},
|
||||
"progress": {
|
||||
"growth_progress": 50,
|
||||
"light_status": 75,
|
||||
"water_status": 65,
|
||||
"yield_progress": 2.5,
|
||||
"yield_current": 12.4,
|
||||
"yield_rate_current": 0.087,
|
||||
},
|
||||
"chart": {
|
||||
"labels": _labels,
|
||||
"height_history": _height,
|
||||
"leaf_history": _leaf,
|
||||
"yield_history": _yield,
|
||||
"yield_rate_history": _yield_rate,
|
||||
},
|
||||
}
|
||||
|
||||
YIELD_PREDICTION_CARD = {
|
||||
"id": "yield_prediction",
|
||||
"title": "پیشبینی عملکرد",
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .mock_data import CONFIG_SLIDERS_ONLY
|
||||
|
||||
|
||||
START_ENVIRONMENT_KEYS = [
|
||||
item["key"]
|
||||
for item in CONFIG_SLIDERS_ONLY["sliders"]
|
||||
if item["key"] != "growth_speed"
|
||||
]
|
||||
|
||||
|
||||
def _defaults_from_sliders():
|
||||
return {
|
||||
item["key"]: item["default_value"]
|
||||
for item in CONFIG_SLIDERS_ONLY["sliders"]
|
||||
}
|
||||
|
||||
|
||||
START_REQUEST_EXAMPLE = {
|
||||
"environment": {
|
||||
key: value for key, value in _defaults_from_sliders().items() if key != "growth_speed"
|
||||
},
|
||||
"growth_speed": _defaults_from_sliders().get("growth_speed", 1.5),
|
||||
}
|
||||
|
||||
|
||||
START_REQUEST_EXAMPLE_STATIC = {
|
||||
"environment": {
|
||||
"light": 75,
|
||||
"water": 65,
|
||||
"soil_ph": 6.5,
|
||||
},
|
||||
"growth_speed": 1.5,
|
||||
}
|
||||
|
||||
|
||||
def success_response():
|
||||
return {"status": "success"}
|
||||
|
||||
|
||||
def success_with_data(data):
|
||||
return {"status": "success", "data": data}
|
||||
|
||||
|
||||
class YieldPredictionCardSerializer(serializers.Serializer):
|
||||
id = serializers.CharField(required=False, allow_blank=True)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from .mock_data import HARVEST_PREDICTION_CARD, YIELD_PREDICTION_CARD, YIELD_PREDICTION_CHART
|
||||
from .models import YieldHarvestPredictionLog
|
||||
|
||||
|
||||
def get_yield_harvest_summary_data(farm=None):
|
||||
data = {
|
||||
"yield_prediction_card": deepcopy(YIELD_PREDICTION_CARD),
|
||||
"yield_prediction_chart": deepcopy(YIELD_PREDICTION_CHART),
|
||||
"harvest_prediction_card": deepcopy(HARVEST_PREDICTION_CARD),
|
||||
}
|
||||
|
||||
if farm is None:
|
||||
return data
|
||||
|
||||
log = YieldHarvestPredictionLog.objects.filter(farm=farm).first()
|
||||
if log is None:
|
||||
return data
|
||||
|
||||
if log.yield_stats:
|
||||
data["yield_prediction_card"]["stats"] = log.yield_stats
|
||||
if log.yield_chip_text:
|
||||
data["yield_prediction_card"]["chipText"] = log.yield_chip_text
|
||||
if log.chart_data:
|
||||
data["yield_prediction_chart"] = deepcopy(log.chart_data)
|
||||
|
||||
if log.harvest_date:
|
||||
data["harvest_prediction_card"]["date"] = log.harvest_date.isoformat()
|
||||
if log.days_until_harvest is not None:
|
||||
data["harvest_prediction_card"]["daysUntil"] = log.days_until_harvest
|
||||
if log.optimal_window_start:
|
||||
data["harvest_prediction_card"]["optimalWindowStart"] = log.optimal_window_start.isoformat()
|
||||
if log.optimal_window_end:
|
||||
data["harvest_prediction_card"]["optimalWindowEnd"] = log.optimal_window_end.isoformat()
|
||||
|
||||
return data
|
||||
+25
-1
@@ -1,6 +1,30 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import YieldHarvestSummaryView
|
||||
from .views import (
|
||||
ConfigView,
|
||||
EnvironmentView,
|
||||
ResetView,
|
||||
StartView,
|
||||
StateView,
|
||||
StopView,
|
||||
YieldHarvestSummaryView,
|
||||
)
|
||||
|
||||
ConfigView.__module__ = "plant_simulator.views"
|
||||
EnvironmentView.__module__ = "plant_simulator.views"
|
||||
ResetView.__module__ = "plant_simulator.views"
|
||||
StartView.__module__ = "plant_simulator.views"
|
||||
StateView.__module__ = "plant_simulator.views"
|
||||
StopView.__module__ = "plant_simulator.views"
|
||||
|
||||
plant_simulator_urlpatterns = [
|
||||
path("config/", ConfigView.as_view(), name="plant-simulator-config"),
|
||||
path("state/", StateView.as_view(), name="plant-simulator-state"),
|
||||
path("start/", StartView.as_view(), name="plant-simulator-start"),
|
||||
path("stop/", StopView.as_view(), name="plant-simulator-stop"),
|
||||
path("reset/", ResetView.as_view(), name="plant-simulator-reset"),
|
||||
path("environment/", EnvironmentView.as_view(), name="plant-simulator-environment"),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
path("summary/", YieldHarvestSummaryView.as_view(), name="yield-harvest-summary"),
|
||||
|
||||
+62
-7
@@ -1,12 +1,8 @@
|
||||
"""
|
||||
Yield & Harvest Prediction API views.
|
||||
Response format: {"status": "success", "data": <payload>}. HTTP 200 only.
|
||||
Fetches all three prediction payloads (yield card, yield chart, harvest card)
|
||||
from the AI external adapter in a single call and persists a log entry
|
||||
if a valid farm_uuid is provided.
|
||||
Yield & Harvest Prediction and Plant Simulator API views.
|
||||
"""
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
@@ -15,8 +11,67 @@ from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from config.swagger import status_response
|
||||
from external_api_adapter import request as external_api_request
|
||||
from farm_hub.models import FarmHub
|
||||
from .mock_data import CONFIG_SLIDERS_ONLY, START_RESPONSE_DATA, STATE_RESPONSE_DATA
|
||||
from .models import YieldHarvestPredictionLog
|
||||
from .serializers import YieldHarvestSummarySerializer
|
||||
from .serializers import YieldHarvestSummarySerializer, success_response, success_with_data
|
||||
|
||||
|
||||
class ConfigView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Plant Simulator"],
|
||||
responses={200: status_response("PlantSimulatorConfigResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(success_with_data(CONFIG_SLIDERS_ONLY), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class StateView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Plant Simulator"],
|
||||
responses={200: status_response("PlantSimulatorStateResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def get(self, request):
|
||||
return Response(success_with_data(STATE_RESPONSE_DATA), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class StartView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Plant Simulator"],
|
||||
request=OpenApiTypes.OBJECT,
|
||||
responses={200: status_response("PlantSimulatorStartResponse", data=serializers.JSONField())},
|
||||
)
|
||||
def post(self, request):
|
||||
return Response(success_with_data(START_RESPONSE_DATA), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class StopView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Plant Simulator"],
|
||||
request=OpenApiTypes.OBJECT,
|
||||
responses={200: status_response("PlantSimulatorStopResponse")},
|
||||
)
|
||||
def post(self, request):
|
||||
return Response(success_response(), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ResetView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Plant Simulator"],
|
||||
request=OpenApiTypes.OBJECT,
|
||||
responses={200: status_response("PlantSimulatorResetResponse")},
|
||||
)
|
||||
def post(self, request):
|
||||
return Response(success_response(), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class EnvironmentView(APIView):
|
||||
@extend_schema(
|
||||
tags=["Plant Simulator"],
|
||||
request=OpenApiTypes.OBJECT,
|
||||
responses={200: status_response("PlantSimulatorEnvironmentResponse")},
|
||||
)
|
||||
def patch(self, request):
|
||||
return Response(success_response(), status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class YieldHarvestSummaryView(APIView):
|
||||
|
||||
Reference in New Issue
Block a user