2026-04-25 17:22:41 +03:30
|
|
|
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
|
2026-05-05 21:02:12 +03:30
|
|
|
from rag.failure_contract import RAGServiceError
|
2026-04-25 17:22:41 +03:30
|
|
|
|
|
|
|
|
from .serializers import (
|
|
|
|
|
SoilAnomalyDetectionRequestSerializer,
|
|
|
|
|
SoilAnomalyDetectionResponseSerializer,
|
|
|
|
|
SoilHealthSummaryRequestSerializer,
|
|
|
|
|
SoilHealthSummaryResponseSerializer,
|
|
|
|
|
SoilMoistureHeatmapRequestSerializer,
|
|
|
|
|
SoilMoistureHeatmapResponseSerializer,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SoileHeatmapEnvelopeSerializer = build_envelope_serializer(
|
|
|
|
|
"SoileHeatmapEnvelopeSerializer",
|
|
|
|
|
SoilMoistureHeatmapResponseSerializer,
|
|
|
|
|
)
|
|
|
|
|
SoileErrorSerializer = build_envelope_serializer(
|
|
|
|
|
"SoileErrorSerializer",
|
|
|
|
|
data_required=False,
|
|
|
|
|
allow_null=True,
|
|
|
|
|
)
|
|
|
|
|
SoileAnomalyEnvelopeSerializer = build_envelope_serializer(
|
|
|
|
|
"SoileAnomalyEnvelopeSerializer",
|
|
|
|
|
SoilAnomalyDetectionResponseSerializer,
|
|
|
|
|
)
|
|
|
|
|
SoileHealthEnvelopeSerializer = build_envelope_serializer(
|
|
|
|
|
"SoileHealthEnvelopeSerializer",
|
|
|
|
|
SoilHealthSummaryResponseSerializer,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SoilMoistureHeatmapView(APIView):
|
|
|
|
|
@extend_schema(
|
|
|
|
|
tags=["Soile"],
|
|
|
|
|
summary="دریافت heatmap رطوبت خاک مزرعه",
|
|
|
|
|
description=(
|
|
|
|
|
"با دریافت farm_uuid، heatmap رطوبت خاک را با وزن دهی زمانی/فضایی، "
|
|
|
|
|
"mask مرز مزرعه و برآورد عدم قطعیت از app مستقل soile برمی گرداند."
|
|
|
|
|
),
|
|
|
|
|
request=SoilMoistureHeatmapRequestSerializer,
|
|
|
|
|
responses={
|
|
|
|
|
200: build_response(
|
|
|
|
|
SoileHeatmapEnvelopeSerializer,
|
|
|
|
|
"داده heatmap رطوبت خاک مزرعه با موفقیت بازگردانده شد.",
|
|
|
|
|
),
|
|
|
|
|
400: build_response(
|
|
|
|
|
SoileErrorSerializer,
|
|
|
|
|
"داده ورودی نامعتبر است.",
|
|
|
|
|
),
|
|
|
|
|
404: build_response(
|
|
|
|
|
SoileErrorSerializer,
|
|
|
|
|
"مزرعه یافت نشد.",
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
examples=[
|
|
|
|
|
OpenApiExample(
|
|
|
|
|
"نمونه درخواست soile",
|
|
|
|
|
value={"farm_uuid": "11111111-1111-1111-1111-111111111111"},
|
|
|
|
|
request_only=True,
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def post(self, request):
|
|
|
|
|
serializer = SoilMoistureHeatmapRequestSerializer(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("soile").get_soil_moisture_service()
|
|
|
|
|
try:
|
|
|
|
|
data = service.get_heatmap(farm_uuid=str(serializer.validated_data["farm_uuid"]))
|
|
|
|
|
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,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SoilHealthSummaryView(APIView):
|
|
|
|
|
@extend_schema(
|
|
|
|
|
tags=["Soile"],
|
|
|
|
|
summary="خلاصه سلامت و رطوبت خاک مزرعه",
|
|
|
|
|
description="با دریافت farm_uuid، امتیاز سلامت خاک/سنسور و میانگین رطوبت فعلی خاک را برمی گرداند.",
|
|
|
|
|
request=SoilHealthSummaryRequestSerializer,
|
|
|
|
|
responses={
|
|
|
|
|
200: build_response(SoileHealthEnvelopeSerializer, "خلاصه سلامت خاک با موفقیت بازگردانده شد."),
|
|
|
|
|
400: build_response(SoileErrorSerializer, "داده ورودی نامعتبر است."),
|
|
|
|
|
404: build_response(SoileErrorSerializer, "مزرعه یافت نشد."),
|
|
|
|
|
},
|
|
|
|
|
examples=[
|
|
|
|
|
OpenApiExample(
|
|
|
|
|
"نمونه درخواست soil health",
|
|
|
|
|
value={"farm_uuid": "11111111-1111-1111-1111-111111111111"},
|
|
|
|
|
request_only=True,
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def post(self, request):
|
|
|
|
|
serializer = SoilHealthSummaryRequestSerializer(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("soile").get_soil_health_service()
|
|
|
|
|
try:
|
|
|
|
|
data = service.get_health_summary(farm_uuid=str(serializer.validated_data["farm_uuid"]))
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SoilAnomalyDetectionView(APIView):
|
|
|
|
|
@extend_schema(
|
|
|
|
|
tags=["Soile"],
|
|
|
|
|
summary="تحلیل ناهنجاری خاک با کمک RAG",
|
|
|
|
|
description="با دریافت farm_uuid، ناهنجاری های آماری داده های خاک را استخراج می کند و تفسیر تخصصی آن را با پایگاه دانش و tone مستقل برمی گرداند.",
|
|
|
|
|
request=SoilAnomalyDetectionRequestSerializer,
|
|
|
|
|
responses={
|
|
|
|
|
200: build_response(
|
|
|
|
|
SoileAnomalyEnvelopeSerializer,
|
|
|
|
|
"خروجی تحلیل ناهنجاری خاک با موفقیت بازگردانده شد.",
|
|
|
|
|
),
|
|
|
|
|
400: build_response(
|
|
|
|
|
SoileErrorSerializer,
|
|
|
|
|
"داده ورودی نامعتبر است.",
|
|
|
|
|
),
|
|
|
|
|
404: build_response(
|
|
|
|
|
SoileErrorSerializer,
|
|
|
|
|
"مزرعه یافت نشد.",
|
|
|
|
|
),
|
|
|
|
|
500: build_response(
|
|
|
|
|
SoileErrorSerializer,
|
|
|
|
|
"خطا در تحلیل ناهنجاری خاک.",
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
examples=[
|
|
|
|
|
OpenApiExample(
|
|
|
|
|
"نمونه درخواست anomaly",
|
|
|
|
|
value={"farm_uuid": "11111111-1111-1111-1111-111111111111"},
|
|
|
|
|
request_only=True,
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def post(self, request):
|
|
|
|
|
serializer = SoilAnomalyDetectionRequestSerializer(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("soile").get_soil_anomaly_service()
|
|
|
|
|
try:
|
|
|
|
|
data = service.get_anomaly_detection(
|
|
|
|
|
farm_uuid=str(serializer.validated_data["farm_uuid"])
|
|
|
|
|
)
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
return Response(
|
|
|
|
|
{"code": 404, "msg": str(exc), "data": None},
|
|
|
|
|
status=status.HTTP_404_NOT_FOUND,
|
|
|
|
|
)
|
2026-05-05 21:02:12 +03:30
|
|
|
except RAGServiceError as exc:
|
|
|
|
|
return Response(
|
|
|
|
|
{"code": exc.http_status, "msg": exc.contract.message, "data": exc.to_dict()},
|
|
|
|
|
status=exc.http_status,
|
|
|
|
|
)
|
2026-04-25 17:22:41 +03:30
|
|
|
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": data},
|
|
|
|
|
status=status.HTTP_200_OK,
|
|
|
|
|
)
|