from django.apps import apps 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 from .serializers import ( FertilizationPlanParserRequestSerializer, FertilizationPlanParserResponseSerializer, FertilizationRecommendationResponseDataSerializer, FertilizationRecommendRequestSerializer, ) FertilizationValidationErrorSerializer = build_envelope_serializer( "FertilizationValidationErrorSerializer", data_required=False, allow_null=True, ) FertilizationResponseSerializer = build_envelope_serializer( "FertilizationResponseSerializer", data_schema=FertilizationRecommendationResponseDataSerializer, ) FertilizationPlanParserEnvelopeSerializer = build_envelope_serializer( "FertilizationPlanParserEnvelopeSerializer", data_schema=FertilizationPlanParserResponseSerializer, ) class FertilizationRecommendView(APIView): """ توصیه کودهی ساختاریافته با ترکیب RAG و optimizer شبیه سازی. """ @extend_schema( tags=["Fertilization Recommendation"], summary="درخواست توصیه کودهی ساختاریافته", description=( "داده های مزرعه، گیاه و مرحله رشد را دریافت می کند و " "خروجی نهایی بهینه شده با ترکیب RAG و optimizer مبتنی بر crop_simulation/PCSE را برمی گرداند." ), request=FertilizationRecommendRequestSerializer, responses={ 200: build_response( FertilizationResponseSerializer, "توصیه کودهی ساختاریافته با موفقیت تولید شد.", ), 400: build_response( FertilizationValidationErrorSerializer, "پارامتر ورودی نامعتبر است.", ), 500: build_response( FertilizationValidationErrorSerializer, "خطا در تولید توصیه کودهی.", ), }, examples=[ OpenApiExample( "نمونه درخواست", value={ "farm_uuid": "11111111-1111-1111-1111-111111111111", "crop_id": "wheat", "growth_stage": "flowering", }, request_only=True, ), OpenApiExample( "نمونه پاسخ", value={ "code": 200, "msg": "success", "data": { "primary_recommendation": { "fertilizer_code": "15-10-30", "fertilizer_name": "کود کامل 15-10-30", "display_title": "کود کامل 15-10-30", "fertilizer_type": "NPK", "npk_ratio": {"n": 15, "p": 10, "k": 30, "label": "15-10-30"}, "application_method": { "id": "foliar_fertigation", "label": "کودآبیاری یا محلول پاشی سبک", }, "application_interval": {"value": 14, "unit": "day", "label": "هر 14 روز"}, "dosage": { "base_amount_per_hectare": 65, "base_amount_per_square_meter": 0.0065, "unit": "kg", "label": "65 کیلوگرم در هکتار", "calculation_basis": "crop_simulation_heuristic", }, "reasoning": "این ترکیب برای مرحله گلدهی و توازن نیازهای تغذیه ای مناسب است.", "summary": "برای پشتیبانی از گلدهی و کاهش تنش تغذیه ای پیشنهاد می شود.", }, "nutrient_analysis": { "macro": [ { "key": "n", "name": "نیتروژن (N)", "value": 15, "unit": "percent", "description": "نیتروژن برای حفظ رشد رویشی مهم است.", } ], "micro": [], }, "application_guide": { "safety_warning": "در ساعات خنک مصرف شود و از اختلاط ناسازگار خودداری کنید.", "steps": [ {"step_number": 1, "title": "آماده سازی", "description": "دوز را آماده کنید."}, {"step_number": 2, "title": "تزریق یا پخش", "description": "طبق روش مصرف اجرا کنید."}, {"step_number": 3, "title": "پایش", "description": "پاسخ مزرعه را بررسی کنید."}, ], }, "alternative_recommendations": [], }, }, response_only=True, ), ], ) def post(self, request): from rag.services.fertilization import get_fertilization_recommendation serializer = FertilizationRecommendRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) validated = serializer.validated_data try: result = get_fertilization_recommendation( farm_uuid=validated["farm_uuid"], plant_name=validated.get("plant_name"), crop_id=validated.get("crop_id"), growth_stage=validated.get("growth_stage"), query=validated.get("query"), ) except Exception as exc: return Response( {"code": 500, "msg": f"خطا در تولید توصیه کودهی: {exc}", "data": None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) final_result = result.get("data") if isinstance(result, dict) else None if not isinstance(final_result, dict): final_result = {"sections": result.get("sections", [])} if isinstance(result, dict) else {} return Response( {"code": 200, "msg": "success", "data": final_result}, status=status.HTTP_200_OK, ) class FertilizationPlanParserView(APIView): @extend_schema( tags=["Fertilization Recommendation"], summary="استخراج برنامه کودهی از متن آزاد", description=( "توضیح متنی کاربر درباره برنامه کودهی را می گیرد و آن را به JSON ساختاریافته تبدیل می کند. " "اگر اطلاعات کافی نباشد، سوالات تکمیلی لازم را برمی گرداند تا در درخواست بعدی پاسخ داده شوند." ), request=FertilizationPlanParserRequestSerializer, responses={ 200: build_response( FertilizationPlanParserEnvelopeSerializer, "نتیجه استخراج یا سوالات تکمیلی برنامه کودهی.", ), 400: build_response( FertilizationValidationErrorSerializer, "داده ورودی نامعتبر است.", ), 500: build_response( FertilizationValidationErrorSerializer, "خطا در پردازش برنامه کودهی.", ), }, examples=[ OpenApiExample( "نمونه درخواست کامل", value={ "message": "برای گندم در مرحله پنجه زنی هر 12 روز یک بار 20-20-20 به مقدار 35 کیلوگرم در هکتار از طریق کودآبیاری می دهم.", "farm_uuid": "11111111-1111-1111-1111-111111111111", }, request_only=True, ), OpenApiExample( "نمونه درخواست تکمیلی", value={ "partial_plan": { "crop_name": "گندم", "applications": [{"fertilizer_name": "20-20-20"}], }, "answers": { "amount": "35 کیلوگرم در هکتار", "timing": "هر 12 روز یک بار", }, }, request_only=True, ), ], ) def post(self, request): serializer = FertilizationPlanParserRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) validated = serializer.validated_data service = apps.get_app_config("fertilization").get_free_text_plan_parser_service() try: result = service.parse_plan( message=validated.get("message", ""), answers=validated.get("answers"), partial_plan=validated.get("partial_plan"), farm_uuid=validated.get("farm_uuid"), ) except Exception as exc: return Response( {"code": 500, "msg": f"خطا در پردازش برنامه کودهی: {exc}", "data": None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) return Response( {"code": 200, "msg": "موفق", "data": result}, status=status.HTTP_200_OK, )