from __future__ import annotations from drf_spectacular.utils import OpenApiExample, extend_schema from rest_framework import status from rest_framework.response import Response from rest_framework.views import APIView from config.openapi import ( build_envelope_serializer, build_response, build_task_status_data_serializer, ) from .growth_simulation import MAX_PAGE_SIZE, paginate_growth_stages from .serializers import ( GrowthSimulationQueuedSerializer, GrowthSimulationRequestSerializer, GrowthSimulationResultSerializer, ) from .tasks import run_growth_simulation_task GrowthSimulationQueuedResponseSerializer = build_envelope_serializer( "GrowthSimulationQueuedResponseSerializer", GrowthSimulationQueuedSerializer, ) GrowthSimulationStatusResponseSerializer = build_envelope_serializer( "GrowthSimulationStatusResponseSerializer", build_task_status_data_serializer( "GrowthSimulationTaskStatusDataSerializer", GrowthSimulationResultSerializer, ), ) GrowthSimulationErrorSerializer = build_envelope_serializer( "GrowthSimulationErrorSerializer", data_required=False, allow_null=True, ) def _get_async_result(task_id: str): from celery.result import AsyncResult return AsyncResult(task_id) def _coerce_positive_int(value, default: int) -> int: try: parsed = int(value) except (TypeError, ValueError): return default return max(parsed, 1) class PlantGrowthSimulationView(APIView): @extend_schema( tags=["Crop Simulation"], summary="شروع شبیه سازی رشد گیاه", description=( "نوع گیاه و پارامترهای متغیر رشد را می گیرد، " "شبیه سازی را داخل Celery اجرا می کند و فقط task_id برمی گرداند." ), request=GrowthSimulationRequestSerializer, responses={ 202: build_response( GrowthSimulationQueuedResponseSerializer, "تسک شبیه سازی رشد گیاه در صف قرار گرفت.", ), 400: build_response( GrowthSimulationErrorSerializer, "داده ورودی نامعتبر است.", ), }, examples=[ OpenApiExample( "نمونه درخواست با weather مستقیم", value={ "plant_name": "گوجه‌فرنگی", "dynamic_parameters": ["DVS", "LAI", "TAGP", "TWSO", "SM"], "weather": [ { "DAY": "2026-04-01", "LAT": 35.7, "LON": 51.4, "TMIN": 12, "TMAX": 24, "RAIN": 0.0, "ET0": 0.32, } ], "soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.0}, "site_parameters": {"WAV": 40.0}, "page_size": 2, }, request_only=True, ), OpenApiExample( "نمونه درخواست با farm", value={ "plant_name": "گوجه‌فرنگی", "dynamic_parameters": ["DVS", "LAI", "TAGP"], "farm_uuid": "550e8400-e29b-41d4-a716-446655440000", }, request_only=True, ), ], ) def post(self, request): serializer = GrowthSimulationRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) task = run_growth_simulation_task.delay(serializer.validated_data) return Response( { "code": 202, "msg": "تسک شبیه سازی رشد در صف قرار گرفت.", "data": { "task_id": task.id, "status_url": f"/api/crop-simulation/growth/{task.id}/status/", "plant_name": serializer.validated_data["plant_name"], }, }, status=status.HTTP_202_ACCEPTED, ) class PlantGrowthSimulationStatusView(APIView): @extend_schema( tags=["Crop Simulation"], summary="وضعیت شبیه سازی رشد گیاه", description="وضعیت تسک Celery را برمی گرداند و در صورت موفقیت مراحل رشد را به صورت صفحه بندی شده بازمی گرداند.", responses={ 200: build_response( GrowthSimulationStatusResponseSerializer, "وضعیت فعلی تسک شبیه سازی رشد گیاه.", ) }, ) def get(self, request, task_id: str): result = _get_async_result(task_id) payload = {"task_id": task_id, "status": result.state} if result.state == "PENDING": payload["message"] = "تسک در صف یا یافت نشد." elif result.state == "PROGRESS": payload["progress"] = result.info elif result.state == "SUCCESS": task_result = dict(result.result or {}) page = _coerce_positive_int(request.query_params.get("page", 1), 1) page_size = min( _coerce_positive_int( request.query_params.get("page_size", task_result.get("default_page_size", 10)), 10, ), MAX_PAGE_SIZE, ) paginated = paginate_growth_stages( task_result.get("stage_timeline", []), page=page, page_size=page_size, ) task_result["stages_page"] = paginated["items"] task_result["pagination"] = paginated["pagination"] payload["result"] = task_result elif result.state == "FAILURE": payload["error"] = str(result.result) return Response( {"code": 200, "msg": "success", "data": payload}, status=status.HTTP_200_OK, )