import json from drf_spectacular.utils import OpenApiExample, extend_schema from rest_framework import status from rest_framework.parsers import FormParser, JSONParser, MultiPartParser from rest_framework.response import Response from rest_framework.views import APIView from config.openapi import build_envelope_serializer, build_response from rag.failure_contract import RAGServiceError from rag.chat import encode_uploaded_image from rag.services import get_pest_disease_detection, get_pest_disease_risk from .serializers import ( PestDiseaseDetectionRequestSerializer, PestDiseaseRiskRequestSerializer, ) PestDiseaseValidationErrorSerializer = build_envelope_serializer( "PestDiseaseValidationErrorSerializer", data_required=False, allow_null=True, ) PestDiseaseDetectionResponseSerializer = build_envelope_serializer( "PestDiseaseDetectionResponseSerializer", data_schema=None, ) PestDiseaseRiskResponseSerializer = build_envelope_serializer( "PestDiseaseRiskResponseSerializer", data_schema=None, ) class _ImageMixin: parser_classes = [JSONParser, MultiPartParser, FormParser] def _collect_uploaded_images(self, request): images = [] for uploaded in request.FILES.getlist("images"): images.append(encode_uploaded_image(uploaded)) single_image = request.FILES.get("image") if single_image is not None: images.append(encode_uploaded_image(single_image)) image_urls = request.data.get("image_urls") if isinstance(image_urls, str) and image_urls.strip(): try: parsed = json.loads(image_urls) except (json.JSONDecodeError, ValueError): parsed = [image_urls] image_urls = parsed if isinstance(image_urls, list): for item in image_urls: if isinstance(item, str) and item.strip(): images.append({"url": item.strip(), "detail": "auto"}) elif isinstance(item, dict) and isinstance(item.get("url"), str): images.append({"url": item["url"].strip(), "detail": item.get("detail", "auto")}) return images class PestDiseaseDetectionView(_ImageMixin, APIView): @extend_schema( tags=["Pest & Disease"], summary="تشخیص آفت یا بیماری از روی تصویر", description="با دریافت farm_uuid و حداقل یک تصویر، تصویر گیاه را با کمک RAG بررسی می کند و نتیجه تشخیص را برمی گرداند.", request=PestDiseaseDetectionRequestSerializer, responses={ 200: build_response(PestDiseaseDetectionResponseSerializer, "نتیجه تشخیص آفت/بیماری."), 400: build_response(PestDiseaseValidationErrorSerializer, "پارامتر ورودی نامعتبر است."), 500: build_response(PestDiseaseValidationErrorSerializer, "خطا در تحلیل تصویر گیاه."), }, examples=[ OpenApiExample( "نمونه درخواست تشخیص", value={ "farm_uuid": "11111111-1111-1111-1111-111111111111", "plant_name": "گوجه فرنگی", "query": "این برگ ها مشکوک به آفت هستند؟", "image_urls": ["https://example.com/leaf.jpg"], }, request_only=True, ), ], ) def post(self, request): serializer = PestDiseaseDetectionRequestSerializer(data=request.data) if not serializer.is_valid(): return Response( {"code": 400, "msg": "داده نامعتبر.", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST, ) images = self._collect_uploaded_images(request) if not images: return Response( {"code": 400, "msg": "حداقل یک تصویر باید ارسال شود.", "data": None}, status=status.HTTP_400_BAD_REQUEST, ) validated = serializer.validated_data try: result = get_pest_disease_detection( farm_uuid=validated["farm_uuid"], plant_name=validated.get("plant_name"), query=validated.get("query"), images=images, ) except RAGServiceError as exc: return Response( {"code": exc.http_status, "msg": exc.contract.message, "data": exc.to_dict()}, status=exc.http_status, ) 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": "success", "data": result}, status=status.HTTP_200_OK) class PestDiseaseRiskView(APIView): @extend_schema( tags=["Pest & Disease"], summary="پیش بینی ریسک آفات و بیماری", description="با دریافت farm_uuid، داده های مزرعه و پایگاه دانش تخصصی را به RAG می دهد و ریسک آفات و بیماری را برمی گرداند.", request=PestDiseaseRiskRequestSerializer, responses={ 200: build_response(PestDiseaseRiskResponseSerializer, "خروجی پیش بینی ریسک آفات و بیماری."), 400: build_response(PestDiseaseValidationErrorSerializer, "پارامتر ورودی نامعتبر است."), 500: build_response(PestDiseaseValidationErrorSerializer, "خطا در پیش بینی ریسک آفات و بیماری."), }, examples=[ OpenApiExample( "نمونه درخواست ریسک", value={ "farm_uuid": "11111111-1111-1111-1111-111111111111", "plant_name": "گوجه فرنگی", "growth_stage": "گلدهی", }, request_only=True, ), ], ) def post(self, request): serializer = PestDiseaseRiskRequestSerializer(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_pest_disease_risk( farm_uuid=validated["farm_uuid"], plant_name=validated.get("plant_name"), growth_stage=validated.get("growth_stage"), query=validated.get("query"), ) except RAGServiceError as exc: return Response( {"code": exc.http_status, "msg": exc.contract.message, "data": exc.to_dict()}, status=exc.http_status, ) 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": "success", "data": result}, status=status.HTTP_200_OK)