Files
2026-05-11 03:27:21 +03:30

495 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
)