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 .models import IrrigationMethod from .serializers import ( IrrigationMethodSerializer, IrrigationPlanParserRequestSerializer, IrrigationPlanParserResponseSerializer, IrrigationRecommendRequestSerializer, WaterStressRequestSerializer, WaterStressResponseSerializer, ) IrrigationMethodListResponseSerializer = build_envelope_serializer( "IrrigationMethodListResponseSerializer", IrrigationMethodSerializer, many=True, ) IrrigationMethodDetailResponseSerializer = build_envelope_serializer( "IrrigationMethodDetailResponseSerializer", IrrigationMethodSerializer, ) IrrigationValidationErrorSerializer = build_envelope_serializer( "IrrigationValidationErrorSerializer", data_required=False, allow_null=True, ) IrrigationRecommendResponseSerializer = build_envelope_serializer( "IrrigationRecommendResponseSerializer", data_schema=None, ) IrrigationPlanParserEnvelopeSerializer = build_envelope_serializer( "IrrigationPlanParserEnvelopeSerializer", IrrigationPlanParserResponseSerializer, ) WaterStressEnvelopeSerializer = build_envelope_serializer( "WaterStressEnvelopeSerializer", WaterStressResponseSerializer, ) IRRIGATION_RECOMMENDATION_INTERNAL_KEYS = { "raw_response", "simulation_optimizer", "selected_irrigation_method", } def _prepare_irrigation_recommendation_response(value): if isinstance(value, dict): cleaned = {} for key, item in value.items(): if key in IRRIGATION_RECOMMENDATION_INTERNAL_KEYS or item is None: continue normalized = _prepare_irrigation_recommendation_response(item) if normalized is not None: cleaned[key] = normalized return cleaned if isinstance(value, list): cleaned_items = [] for item in value: normalized = _prepare_irrigation_recommendation_response(item) if normalized is not None: cleaned_items.append(normalized) return cleaned_items return value class IrrigationMethodListCreateView(APIView): """لیست تمام روش‌های آبیاری و ایجاد روش جدید.""" @extend_schema( tags=["Irrigation"], summary="لیست روش‌های آبیاری", description="لیست تمام روش‌های آبیاری ذخیره‌شده را برمی‌گرداند.", responses={ 200: build_response( IrrigationMethodListResponseSerializer, "لیست روش‌های آبیاری ذخیره‌شده.", ), }, ) def get(self, request): methods = IrrigationMethod.objects.all() serializer = IrrigationMethodSerializer(methods, many=True) return Response( {"code": 200, "msg": "success", "data": serializer.data}, status=status.HTTP_200_OK, ) @extend_schema( tags=["Irrigation"], summary="ایجاد روش آبیاری جدید", description="یک روش آبیاری جدید ایجاد می‌کند.", request=IrrigationMethodSerializer, responses={ 201: build_response( IrrigationMethodDetailResponseSerializer, "روش آبیاری جدید با موفقیت ایجاد شد.", ), 400: build_response( IrrigationValidationErrorSerializer, "داده ورودی نامعتبر است.", ), }, examples=[ OpenApiExample( "نمونه درخواست", value={ "name": "آبیاری قطره‌ای", "category": "موضعی", "description": "آبیاری با دبی کم و فشار مناسب", "water_efficiency_percent": 90.0, "water_pressure_required": "۱-۲ اتمسفر", "flow_rate": "۲-۸ لیتر در ساعت", "coverage_area": "بسته به طراحی سیستم", "soil_type": "تمام انواع خاک", "climate_suitability": "گرم و خشک", }, request_only=True, ), ], ) def post(self, request): serializer = IrrigationMethodSerializer(data=request.data) if not serializer.is_valid(): return Response( {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) serializer.save() return Response( {"code": 201, "msg": "success", "data": serializer.data}, status=status.HTTP_201_CREATED, ) class IrrigationRecommendView(APIView): """ توصیه آبیاری به صورت مستقیم. POST با farm_uuid، plant_name، growth_stage، irrigation_method_name. اطلاعات گیاه از plant app و روش آبیاری از irrigation app دریافت می‌شود. """ @extend_schema( tags=["Irrigation Recommendation"], summary="درخواست توصیه آبیاری", description=( "داده‌های سنسور، گیاه و روش آبیاری را دریافت کرده و " "توصیه آبیاری را مستقیم برمی‌گرداند. " "اطلاعات گیاه از جدول Plant و روش آبیاری از جدول IrrigationMethod بارگذاری می‌شود. " "محاسبات ET₀ و ETc با مدل FAO-56 در بک‌اند انجام می‌شود و مدل زبانی فقط توضیح برنامه آبیاری را تولید می‌کند." ), request=IrrigationRecommendRequestSerializer, responses={ 200: build_response( IrrigationRecommendResponseSerializer, "توصیه آبیاری با موفقیت تولید شد.", ), 400: build_response( IrrigationValidationErrorSerializer, "پارامتر ورودی نامعتبر است.", ), 500: build_response( IrrigationValidationErrorSerializer, "خطا در تولید توصیه آبیاری.", ), }, examples=[ OpenApiExample( "نمونه درخواست", value={ "farm_uuid": "11111111-1111-1111-1111-111111111111", "plant_name": "گوجه‌فرنگی", "growth_stage": "گلدهی", "irrigation_method_name": "آبیاری قطره‌ای", }, request_only=True, ), ], ) def post(self, request): from rag.services.irrigation import get_irrigation_recommendation serializer = IrrigationRecommendRequestSerializer(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 farm_uuid = validated["farm_uuid"] plant_name = validated.get("plant_name") growth_stage = validated.get("growth_stage") irrigation_method_name = validated.get("irrigation_method_name") try: result = get_irrigation_recommendation( farm_uuid=farm_uuid, plant_name=plant_name, growth_stage=growth_stage, irrigation_method_name=irrigation_method_name, ) except Exception as exc: return Response( {"code": 500, "msg": f"خطا در تولید توصیه آبیاری: {exc}", "data": None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) final_result = _prepare_irrigation_recommendation_response(result) or {} return Response( {"code": 200, "msg": "success", "data": final_result}, status=status.HTTP_200_OK, ) class IrrigationPlanParserView(APIView): @extend_schema( tags=["Irrigation Recommendation"], summary="استخراج برنامه آبیاری از متن آزاد", description=( "توضیح متنی کاربر درباره برنامه آبیاری را می گیرد و آن را به JSON ساختاریافته تبدیل می کند. " "اگر اطلاعات کافی نباشد، سوالات تکمیلی لازم را برمی گرداند تا در درخواست بعدی پاسخ داده شوند." ), request=IrrigationPlanParserRequestSerializer, responses={ 200: build_response( IrrigationPlanParserEnvelopeSerializer, "نتیجه استخراج یا سوالات تکمیلی برنامه آبیاری.", ), 400: build_response( IrrigationValidationErrorSerializer, "داده ورودی نامعتبر است.", ), 500: build_response( IrrigationValidationErrorSerializer, "خطا در پردازش برنامه آبیاری.", ), }, examples=[ OpenApiExample( "نمونه درخواست کامل", value={ "message": "برای گوجه فرنگی با آبیاری قطره ای هر سه روز یک بار صبح زود 25 دقیقه آبیاری می کنم و حدود 18 لیتر برای هر بوته می دهم.", "farm_uuid": "11111111-1111-1111-1111-111111111111", }, request_only=True, ), OpenApiExample( "نمونه درخواست تکمیلی", value={ "partial_plan": { "crop_name": "گوجه فرنگی", "irrigation_method": "قطره ای", }, "answers": { "water_amount_per_event": "18 لیتر برای هر بوته", "preferred_time_of_day": "صبح زود", }, }, request_only=True, ), ], ) def post(self, request): serializer = IrrigationPlanParserRequestSerializer(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("irrigation").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, ) class IrrigationMethodDetailView(APIView): """دریافت، ویرایش و حذف یک روش آبیاری.""" def _get_method(self, pk): return IrrigationMethod.objects.filter(pk=pk).first() @extend_schema( tags=["Irrigation"], summary="جزئیات روش آبیاری", description="مشخصات یک روش آبیاری را بر اساس شناسه برمی‌گرداند.", responses={ 200: build_response( IrrigationMethodDetailResponseSerializer, "جزئیات روش آبیاری.", ), 404: build_response( IrrigationValidationErrorSerializer, "روش آبیاری یافت نشد.", ), }, ) def get(self, request, pk): method = self._get_method(pk) if not method: return Response( {"code": 404, "msg": "روش آبیاری یافت نشد.", "data": None}, status=status.HTTP_404_NOT_FOUND, ) serializer = IrrigationMethodSerializer(method) return Response( {"code": 200, "msg": "success", "data": serializer.data}, status=status.HTTP_200_OK, ) class WaterStressView(APIView): @extend_schema( tags=["Irrigation"], summary="شاخص تنش آبی مزرعه", description=( "با دریافت farm_uuid، شاخص تنش آبی مزرعه را با استفاده از شبیه سازی " "crop_simulation و داده هایی مثل رطوبت خاک، ET0، بارش، مرحله رشد و پارامترهای خاک برمی گرداند." ), request=WaterStressRequestSerializer, responses={ 200: build_response(WaterStressEnvelopeSerializer, "شاخص تنش آبی مزرعه."), 400: build_response(IrrigationValidationErrorSerializer, "پارامتر ورودی نامعتبر است."), 404: build_response(IrrigationValidationErrorSerializer, "مزرعه یافت نشد."), }, examples=[ OpenApiExample( "نمونه درخواست water stress", value={ "farm_uuid": "11111111-1111-1111-1111-111111111111", "irrigation_recommendation": {"events": [{"date": "2026-04-25", "amount": 2.5}]}, "fertilization_recommendation": { "events": [{"date": "2026-04-20", "N_amount": 45, "N_recovery": 0.7}] }, }, request_only=True, ) ], ) def post(self, request): serializer = WaterStressRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) service = apps.get_app_config("irrigation").get_water_stress_service() try: data = service.get_water_stress( farm_uuid=serializer.validated_data["farm_uuid"], plant_name=serializer.validated_data.get("plant_name"), irrigation_recommendation=serializer.validated_data.get("irrigation_recommendation"), fertilization_recommendation=serializer.validated_data.get("fertilization_recommendation"), ) except ValueError as exc: return Response( {"code": 404, "msg": str(exc), "data": None}, status=status.HTTP_404_NOT_FOUND, ) return Response({"code": 200, "msg": "success", "data": data}, status=status.HTTP_200_OK) @extend_schema( tags=["Irrigation"], summary="ویرایش کامل روش آبیاری", description="تمام فیلدهای یک روش آبیاری را آپدیت می‌کند.", request=IrrigationMethodSerializer, responses={ 200: build_response( IrrigationMethodDetailResponseSerializer, "روش آبیاری با موفقیت به‌روزرسانی شد.", ), 400: build_response( IrrigationValidationErrorSerializer, "داده ورودی نامعتبر است.", ), 404: build_response( IrrigationValidationErrorSerializer, "روش آبیاری یافت نشد.", ), }, ) def put(self, request, pk): method = self._get_method(pk) if not method: return Response( {"code": 404, "msg": "روش آبیاری یافت نشد.", "data": None}, status=status.HTTP_404_NOT_FOUND, ) serializer = IrrigationMethodSerializer(method, data=request.data) if not serializer.is_valid(): return Response( {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) serializer.save() return Response( {"code": 200, "msg": "success", "data": serializer.data}, status=status.HTTP_200_OK, ) @extend_schema( tags=["Irrigation"], summary="ویرایش جزئی روش آبیاری", description="فقط فیلدهای ارسال‌شده آپدیت می‌شوند.", request=IrrigationMethodSerializer, responses={ 200: build_response( IrrigationMethodDetailResponseSerializer, "روش آبیاری با موفقیت به‌روزرسانی شد.", ), 400: build_response( IrrigationValidationErrorSerializer, "داده ورودی نامعتبر است.", ), 404: build_response( IrrigationValidationErrorSerializer, "روش آبیاری یافت نشد.", ), }, ) def patch(self, request, pk): method = self._get_method(pk) if not method: return Response( {"code": 404, "msg": "روش آبیاری یافت نشد.", "data": None}, status=status.HTTP_404_NOT_FOUND, ) serializer = IrrigationMethodSerializer(method, data=request.data, partial=True) if not serializer.is_valid(): return Response( {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) serializer.save() return Response( {"code": 200, "msg": "success", "data": serializer.data}, status=status.HTTP_200_OK, ) @extend_schema( tags=["Irrigation"], summary="حذف روش آبیاری", description="یک روش آبیاری را حذف می‌کند.", responses={ 200: build_response( IrrigationValidationErrorSerializer, "روش آبیاری با موفقیت حذف شد.", ), 404: build_response( IrrigationValidationErrorSerializer, "روش آبیاری یافت نشد.", ), }, ) def delete(self, request, pk): method = self._get_method(pk) if not method: return Response( {"code": 404, "msg": "روش آبیاری یافت نشد.", "data": None}, status=status.HTTP_404_NOT_FOUND, ) method.delete() return Response( {"code": 200, "msg": "روش آبیاری با موفقیت حذف شد.", "data": None}, status=status.HTTP_200_OK, )