UPDATE
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
CONFIG_RESPONSE_TEMPLATE = {
|
||||
"farmInfo": {
|
||||
"soilType": None,
|
||||
"waterQuality": None,
|
||||
"climateZone": None,
|
||||
},
|
||||
"cropOptions": [
|
||||
{"id": "wheat", "labelKey": "wheat", "icon": "tabler-wheat"},
|
||||
{"id": "corn", "labelKey": "corn", "icon": "tabler-plant-2"},
|
||||
{"id": "cotton", "labelKey": "cotton", "icon": "tabler-flower"},
|
||||
{"id": "saffron", "labelKey": "saffron", "icon": "tabler-flower-2"},
|
||||
{"id": "canola", "labelKey": "canola", "icon": "tabler-leaf"},
|
||||
{"id": "vegetables", "labelKey": "vegetables", "icon": "tabler-carrot"},
|
||||
],
|
||||
"status": "success",
|
||||
"source": "default_template",
|
||||
}
|
||||
|
||||
|
||||
IRRIGATION_DASHBOARD_TEMPLATE = {
|
||||
"title": "آبیاری",
|
||||
"subtitle": "داده توصیه آبیاری هنوز ثبت نشده است.",
|
||||
"avatarIcon": "tabler-droplet",
|
||||
"avatarColor": "primary",
|
||||
"status": "empty",
|
||||
"source": "db",
|
||||
"warnings": ["No persisted irrigation recommendation is available for this farm."],
|
||||
}
|
||||
+87
-18
@@ -1,12 +1,30 @@
|
||||
from copy import deepcopy
|
||||
import logging
|
||||
|
||||
from .mock_data import IRRIGATION_DASHBOARD_RECOMMENDATION, RECOMMEND_RESPONSE_DATA, WATER_NEED_PREDICTION
|
||||
from config.failure_contract import StructuredServiceError
|
||||
|
||||
from .defaults import IRRIGATION_DASHBOARD_TEMPLATE
|
||||
from .models import IrrigationPlan, IrrigationRecommendationRequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IrrigationDataUnavailableError(StructuredServiceError):
|
||||
def __init__(self, *, error_code: str, message: str, details: dict | None = None):
|
||||
super().__init__(
|
||||
error_code=error_code,
|
||||
message=message,
|
||||
source="db",
|
||||
details=details,
|
||||
)
|
||||
|
||||
|
||||
def _extract_result(response_payload):
|
||||
if not isinstance(response_payload, dict):
|
||||
return {}
|
||||
raise IrrigationDataUnavailableError(
|
||||
error_code="invalid_payload",
|
||||
message="Irrigation recommendation payload must be a JSON object.",
|
||||
)
|
||||
|
||||
data = response_payload.get("data")
|
||||
if isinstance(data, dict):
|
||||
@@ -22,24 +40,47 @@ def _extract_result(response_payload):
|
||||
if any(key in response_payload for key in ("plan", "water_balance", "timeline", "sections")):
|
||||
return response_payload
|
||||
|
||||
return {}
|
||||
return None
|
||||
|
||||
|
||||
def _get_latest_result(farm):
|
||||
if farm is None:
|
||||
return {}
|
||||
raise IrrigationDataUnavailableError(
|
||||
error_code="missing_farm",
|
||||
message="Farm instance is required for irrigation result lookup.",
|
||||
)
|
||||
|
||||
for request in IrrigationRecommendationRequest.objects.filter(farm=farm).order_by("-created_at", "-id"):
|
||||
result = _extract_result(request.response_payload)
|
||||
try:
|
||||
result = _extract_result(request.response_payload)
|
||||
except IrrigationDataUnavailableError as exc:
|
||||
logger.error(
|
||||
"Invalid irrigation response payload for farm_id=%s request_id=%s: %s",
|
||||
getattr(farm, "id", None),
|
||||
request.id,
|
||||
exc,
|
||||
)
|
||||
raise IrrigationDataUnavailableError(
|
||||
error_code=exc.contract.error_code,
|
||||
message=f"Invalid irrigation recommendation payload for request_id={request.id}.",
|
||||
details={"farm_id": getattr(farm, "id", None), "request_id": request.id},
|
||||
) from exc
|
||||
if result:
|
||||
return result
|
||||
|
||||
return {}
|
||||
raise IrrigationDataUnavailableError(
|
||||
error_code="no_data",
|
||||
message=f"No irrigation recommendation result found for farm_id={getattr(farm, 'id', None)}.",
|
||||
details={"farm_id": getattr(farm, "id", None)},
|
||||
)
|
||||
|
||||
|
||||
def get_active_plan_payload(farm):
|
||||
if farm is None:
|
||||
return {}
|
||||
raise IrrigationDataUnavailableError(
|
||||
error_code="missing_farm",
|
||||
message="Farm instance is required for active irrigation plan lookup.",
|
||||
)
|
||||
|
||||
plan = (
|
||||
IrrigationPlan.objects.filter(farm=farm, is_active=True, is_deleted=False)
|
||||
@@ -47,15 +88,17 @@ def get_active_plan_payload(farm):
|
||||
.first()
|
||||
)
|
||||
if plan is None or not isinstance(plan.plan_payload, dict):
|
||||
return {}
|
||||
raise IrrigationDataUnavailableError(
|
||||
error_code="no_active_plan",
|
||||
message=f"No active irrigation plan payload found for farm_id={getattr(farm, 'id', None)}.",
|
||||
details={"farm_id": getattr(farm, "id", None)},
|
||||
)
|
||||
|
||||
return deepcopy(plan.plan_payload)
|
||||
|
||||
|
||||
def build_active_plan_context(farm):
|
||||
plan_payload = get_active_plan_payload(farm)
|
||||
if not plan_payload:
|
||||
return {}
|
||||
|
||||
context = {"plan_payload": plan_payload}
|
||||
|
||||
@@ -200,24 +243,37 @@ def _normalize_sections(raw_sections):
|
||||
|
||||
def build_recommendation_response(adapter_payload):
|
||||
result = _extract_result(adapter_payload)
|
||||
fallback_plan = RECOMMEND_RESPONSE_DATA.get("plan", {})
|
||||
if not isinstance(result, dict):
|
||||
raise IrrigationDataUnavailableError(
|
||||
error_code="no_result",
|
||||
message="Irrigation recommendation payload did not include a result object.",
|
||||
)
|
||||
if not isinstance(result.get("plan"), dict):
|
||||
raise IrrigationDataUnavailableError(
|
||||
error_code="invalid_payload",
|
||||
message="Irrigation recommendation payload is missing a valid `plan` object.",
|
||||
)
|
||||
|
||||
return {
|
||||
"plan": _normalize_plan(result.get("plan") or fallback_plan),
|
||||
response = {
|
||||
"plan": _normalize_plan(result.get("plan")),
|
||||
"water_balance": _normalize_water_balance(result.get("water_balance")),
|
||||
"timeline": _normalize_timeline(result.get("timeline")),
|
||||
"sections": _normalize_sections(result.get("sections")),
|
||||
}
|
||||
return response
|
||||
|
||||
|
||||
def get_water_need_prediction_data(farm=None):
|
||||
default_data = deepcopy(WATER_NEED_PREDICTION)
|
||||
result = _get_latest_result(farm)
|
||||
water_balance = result.get("water_balance", {})
|
||||
daily = water_balance.get("daily", [])
|
||||
|
||||
if not daily:
|
||||
return default_data
|
||||
raise IrrigationDataUnavailableError(
|
||||
error_code="empty_daily_data",
|
||||
message=f"Water need prediction data is missing daily entries for farm_id={getattr(farm, 'id', None)}.",
|
||||
details={"farm_id": getattr(farm, "id", None)},
|
||||
)
|
||||
|
||||
categories = [item.get("forecast_date") or f"روز {index + 1}" for index, item in enumerate(daily)]
|
||||
series_data = [float(item.get("gross_irrigation_mm") or 0) for item in daily]
|
||||
@@ -231,9 +287,19 @@ def get_water_need_prediction_data(farm=None):
|
||||
|
||||
|
||||
def get_irrigation_dashboard_recommendation(farm=None):
|
||||
default_item = deepcopy(IRRIGATION_DASHBOARD_RECOMMENDATION)
|
||||
result = _get_latest_result(farm)
|
||||
plan = result.get("plan") or RECOMMEND_RESPONSE_DATA.get("plan", {})
|
||||
default_item = deepcopy(IRRIGATION_DASHBOARD_TEMPLATE)
|
||||
try:
|
||||
result = _get_latest_result(farm)
|
||||
except IrrigationDataUnavailableError as exc:
|
||||
logger.info(
|
||||
"Irrigation dashboard recommendation unavailable for farm_id=%s: %s",
|
||||
getattr(farm, "id", None),
|
||||
exc,
|
||||
)
|
||||
return default_item
|
||||
plan = result.get("plan")
|
||||
if not isinstance(plan, dict):
|
||||
return default_item
|
||||
|
||||
best_time = plan.get("bestTimeOfDay") or "05:00 - 07:00"
|
||||
frequency = plan.get("frequencyPerWeek")
|
||||
@@ -252,5 +318,8 @@ def get_irrigation_dashboard_recommendation(farm=None):
|
||||
default_item["title"] = f"آبیاری: {best_time}"
|
||||
if subtitle_parts:
|
||||
default_item["subtitle"] = ". ".join(subtitle_parts)
|
||||
default_item["status"] = "success"
|
||||
default_item["source"] = "db"
|
||||
default_item["warnings"] = []
|
||||
|
||||
return default_item
|
||||
|
||||
@@ -9,6 +9,7 @@ from farm_hub.models import FarmHub, FarmType
|
||||
from farmer_calendar.models import FarmerCalendarEvent
|
||||
|
||||
from .models import IrrigationPlan, IrrigationRecommendationRequest
|
||||
from .services import IrrigationDataUnavailableError, build_recommendation_response
|
||||
from .views import (
|
||||
IrrigationMethodListView,
|
||||
IrrigationPlanDetailView,
|
||||
@@ -22,6 +23,37 @@ from .views import (
|
||||
)
|
||||
|
||||
|
||||
class IrrigationServiceFailureTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = get_user_model().objects.create_user(
|
||||
username="irrigation-service-user",
|
||||
password="secret123",
|
||||
email="irrigation-service@example.com",
|
||||
phone_number="09120000009",
|
||||
)
|
||||
self.farm_type = FarmType.objects.create(name="زراعی")
|
||||
self.farm = FarmHub.objects.create(owner=self.user, farm_type=self.farm_type, name="Service Farm")
|
||||
|
||||
def test_get_water_need_prediction_raises_structured_error_for_missing_daily_entries(self):
|
||||
IrrigationRecommendationRequest.objects.create(
|
||||
farm=self.farm,
|
||||
response_payload={"data": {"result": {"plan": {"bestTimeOfDay": "05:00"}}}},
|
||||
)
|
||||
|
||||
from .services import get_water_need_prediction_data
|
||||
|
||||
with self.assertRaises(IrrigationDataUnavailableError) as exc_info:
|
||||
get_water_need_prediction_data(self.farm)
|
||||
|
||||
self.assertEqual(exc_info.exception.contract.error_code, "empty_daily_data")
|
||||
|
||||
def test_build_recommendation_response_rejects_non_object_payload(self):
|
||||
with self.assertRaises(IrrigationDataUnavailableError) as exc_info:
|
||||
build_recommendation_response(["not-a-dict"])
|
||||
|
||||
self.assertEqual(exc_info.exception.contract.error_code, "invalid_payload")
|
||||
|
||||
|
||||
class WaterStressViewTests(TestCase):
|
||||
def setUp(self):
|
||||
self.factory = APIRequestFactory()
|
||||
@@ -72,6 +104,8 @@ class WaterStressViewTests(TestCase):
|
||||
self.assertEqual(response.data["data"]["waterStressIndex"], 12)
|
||||
self.assertEqual(response.data["data"]["level"], "پایین")
|
||||
self.assertEqual(response.data["data"]["sourceMetric"], {"soilMoisture": 24})
|
||||
self.assertEqual(response.data["meta"]["flow_type"], "direct_proxy")
|
||||
self.assertEqual(response.data["meta"]["source_service"], "ai_irrigation_water_stress")
|
||||
mock_external_api_request.assert_called_once_with(
|
||||
"ai",
|
||||
"/api/irrigation/water-stress/",
|
||||
@@ -93,6 +127,27 @@ class WaterStressViewTests(TestCase):
|
||||
self.assertEqual(response.data["code"], 404)
|
||||
self.assertEqual(response.data["data"]["farm_uuid"][0], "Farm not found.")
|
||||
|
||||
@patch("irrigation.views.external_api_request")
|
||||
def test_post_returns_upstream_failure_without_masking_as_empty(self, mock_external_api_request):
|
||||
mock_external_api_request.return_value = AdapterResponse(
|
||||
status_code=503,
|
||||
data={"message": "AI unavailable", "status": "error"},
|
||||
)
|
||||
|
||||
request = self.factory.post(
|
||||
"/api/irrigation/water-stress/",
|
||||
{"farm_uuid": str(self.farm.farm_uuid)},
|
||||
format="json",
|
||||
)
|
||||
force_authenticate(request, user=self.user)
|
||||
|
||||
response = WaterStressView.as_view()(request)
|
||||
|
||||
self.assertEqual(response.status_code, 503)
|
||||
self.assertEqual(response.data["data"]["message"], "AI unavailable")
|
||||
self.assertNotEqual(response.data.get("data"), [])
|
||||
self.assertNotEqual(response.data.get("data"), {})
|
||||
|
||||
|
||||
class IrrigationPlanFromTextViewTests(TestCase):
|
||||
def setUp(self):
|
||||
@@ -136,6 +191,8 @@ class IrrigationPlanFromTextViewTests(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data["data"]["status"], "completed")
|
||||
self.assertEqual(response.data["meta"]["flow_type"], "direct_proxy")
|
||||
self.assertEqual(response.data["meta"]["ownership"], "backend")
|
||||
self.assertEqual(IrrigationPlan.objects.count(), 1)
|
||||
plan = IrrigationPlan.objects.get()
|
||||
self.assertEqual(plan.source, IrrigationPlan.SOURCE_FREE_TEXT)
|
||||
@@ -184,6 +241,8 @@ class IrrigationMethodListViewTests(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data["code"], 200)
|
||||
self.assertEqual(response.data["data"][0]["name"], "Drip")
|
||||
self.assertEqual(response.data["meta"]["flow_type"], "direct_proxy")
|
||||
self.assertTrue(response.data["meta"]["live"])
|
||||
mock_external_api_request.assert_called_once_with(
|
||||
"ai",
|
||||
"/api/irrigation/",
|
||||
@@ -208,6 +267,7 @@ class IrrigationMethodListViewTests(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data["data"]["name"], "Drip")
|
||||
self.assertEqual(response.data["meta"]["source_service"], "ai_irrigation")
|
||||
mock_external_api_request.assert_called_once_with(
|
||||
"ai",
|
||||
"/api/irrigation/",
|
||||
@@ -319,6 +379,30 @@ class RecommendViewTests(TestCase):
|
||||
self.assertEqual(response.data["data"]["water_balance"]["active_kc"], 0.93)
|
||||
self.assertEqual(response.data["data"]["timeline"][0]["step_number"], 1)
|
||||
self.assertEqual(response.data["data"]["sections"][0]["type"], "warning")
|
||||
|
||||
@patch("irrigation.views.external_api_request")
|
||||
def test_recommend_view_persists_real_response_and_never_returns_fake_success_on_invalid_payload(self, mock_external_api_request):
|
||||
mock_external_api_request.return_value = AdapterResponse(
|
||||
status_code=200,
|
||||
data={"data": {"result": {"plan": {"bestTimeOfDay": "05:00"}}}},
|
||||
)
|
||||
|
||||
request = self.factory.post(
|
||||
"/api/irrigation/recommend/",
|
||||
{
|
||||
"farm_uuid": str(self.farm.farm_uuid),
|
||||
"plant_name": "گوجه فرنگی",
|
||||
"growth_stage": "گلدهی",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
force_authenticate(request, user=self.user)
|
||||
|
||||
response = RecommendView.as_view()(request)
|
||||
|
||||
self.assertEqual(response.status_code, 502)
|
||||
self.assertEqual(IrrigationRecommendationRequest.objects.count(), 1)
|
||||
self.assertEqual(IrrigationRecommendationRequest.objects.get().status, IrrigationRecommendationRequest.STATUS_ERROR)
|
||||
mock_external_api_request.assert_called_once_with(
|
||||
"ai",
|
||||
"/api/irrigation/recommend/",
|
||||
|
||||
+108
-19
@@ -12,13 +12,14 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from config.integration_contract import build_integration_meta
|
||||
from config.swagger import code_response, status_response
|
||||
from external_api_adapter import request as external_api_request
|
||||
from farm_hub.models import FarmHub
|
||||
from farmer_calendar import PLAN_TYPE_IRRIGATION, delete_plan_events, sync_plan_events
|
||||
from water.serializers import WaterStressIndexSerializer
|
||||
from water.views import WaterStressIndexView
|
||||
from .mock_data import CONFIG_RESPONSE_DATA
|
||||
from .defaults import CONFIG_RESPONSE_TEMPLATE
|
||||
from .models import IrrigationPlan, IrrigationRecommendationRequest
|
||||
from .serializers import (
|
||||
FreeTextPlanParserRequestSerializer,
|
||||
@@ -36,6 +37,7 @@ from .serializers import (
|
||||
)
|
||||
from .services import build_recommendation_response
|
||||
from .services import build_active_plan_context
|
||||
from .services import IrrigationDataUnavailableError
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -86,7 +88,7 @@ class ConfigView(FarmAccessMixin, APIView):
|
||||
)
|
||||
def get(self, request):
|
||||
farm = self._get_farm(request, request.query_params.get("farm_uuid"))
|
||||
data = dict(CONFIG_RESPONSE_DATA)
|
||||
data = dict(CONFIG_RESPONSE_TEMPLATE)
|
||||
data["farm_uuid"] = str(farm.farm_uuid)
|
||||
return Response({"status": "success", "data": data}, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -128,7 +130,19 @@ class IrrigationMethodListView(APIView):
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"code": 200, "msg": "success", "data": self._extract_methods(adapter_response.data)},
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": self._extract_methods(adapter_response.data),
|
||||
"meta": build_integration_meta(
|
||||
flow_type="direct_proxy",
|
||||
source_type="provider",
|
||||
source_service="ai_irrigation",
|
||||
ownership="ai",
|
||||
live=True,
|
||||
cached=False,
|
||||
),
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@@ -157,7 +171,19 @@ class IrrigationMethodListView(APIView):
|
||||
payload = response_data.get("data", response_data)
|
||||
|
||||
return Response(
|
||||
{"code": adapter_response.status_code, "msg": "success", "data": payload},
|
||||
{
|
||||
"code": adapter_response.status_code,
|
||||
"msg": "success",
|
||||
"data": payload,
|
||||
"meta": build_integration_meta(
|
||||
flow_type="direct_proxy",
|
||||
source_type="provider",
|
||||
source_service="ai_irrigation",
|
||||
ownership="ai",
|
||||
live=True,
|
||||
cached=False,
|
||||
),
|
||||
},
|
||||
status=adapter_response.status_code,
|
||||
)
|
||||
|
||||
@@ -188,7 +214,10 @@ class RecommendView(FarmAccessMixin, APIView):
|
||||
@staticmethod
|
||||
def _enrich_ai_payload(payload, farm):
|
||||
enriched_payload = payload.copy()
|
||||
active_plan_context = build_active_plan_context(farm)
|
||||
try:
|
||||
active_plan_context = build_active_plan_context(farm)
|
||||
except IrrigationDataUnavailableError:
|
||||
active_plan_context = None
|
||||
if active_plan_context:
|
||||
enriched_payload["active_irrigation_plan"] = active_plan_context
|
||||
return enriched_payload
|
||||
@@ -224,16 +253,6 @@ class RecommendView(FarmAccessMixin, APIView):
|
||||
)
|
||||
|
||||
response_data = adapter_response.data if isinstance(adapter_response.data, dict) else {}
|
||||
recommendation_data = build_recommendation_response(response_data)
|
||||
|
||||
logger.warning(
|
||||
"Irrigation recommendation response parsed: farm_uuid=%s status_code=%s response_keys=%s sections_count=%s",
|
||||
str(farm.farm_uuid),
|
||||
adapter_response.status_code,
|
||||
sorted(response_data.keys()) if isinstance(response_data, dict) else None,
|
||||
len(recommendation_data["sections"]),
|
||||
)
|
||||
|
||||
recommendation = IrrigationRecommendationRequest.objects.create(
|
||||
farm=farm,
|
||||
crop_id=payload.get("plant_name", ""),
|
||||
@@ -256,6 +275,23 @@ class RecommendView(FarmAccessMixin, APIView):
|
||||
},
|
||||
status=adapter_response.status_code,
|
||||
)
|
||||
try:
|
||||
recommendation_data = build_recommendation_response(response_data)
|
||||
except IrrigationDataUnavailableError as exc:
|
||||
recommendation.status = IrrigationRecommendationRequest.STATUS_ERROR
|
||||
recommendation.save(update_fields=["status"])
|
||||
return Response(
|
||||
{"code": 502, "msg": "error", "data": {"detail": str(exc)}},
|
||||
status=status.HTTP_502_BAD_GATEWAY,
|
||||
)
|
||||
|
||||
logger.warning(
|
||||
"Irrigation recommendation response parsed: farm_uuid=%s status_code=%s response_keys=%s sections_count=%s",
|
||||
str(farm.farm_uuid),
|
||||
adapter_response.status_code,
|
||||
sorted(response_data.keys()) if isinstance(response_data, dict) else None,
|
||||
len(recommendation_data["sections"]),
|
||||
)
|
||||
|
||||
self._create_plan_from_recommendation(recommendation, recommendation_data)
|
||||
|
||||
@@ -272,6 +308,14 @@ class RecommendView(FarmAccessMixin, APIView):
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": recommendation_data,
|
||||
"meta": build_integration_meta(
|
||||
flow_type="backend_owned_data_with_ai_enrichment",
|
||||
source_type="provider",
|
||||
source_service="ai_irrigation",
|
||||
ownership="backend",
|
||||
live=True,
|
||||
cached=False,
|
||||
),
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
@@ -329,7 +373,13 @@ class RecommendationDetailView(FarmAccessMixin, APIView):
|
||||
if recommendation is None:
|
||||
return Response({"code": 404, "msg": "Recommendation not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
data = build_recommendation_response(recommendation.response_payload)
|
||||
try:
|
||||
data = build_recommendation_response(recommendation.response_payload)
|
||||
except IrrigationDataUnavailableError as exc:
|
||||
return Response(
|
||||
{"code": 502, "msg": "error", "data": {"detail": str(exc)}},
|
||||
status=status.HTTP_502_BAD_GATEWAY,
|
||||
)
|
||||
request_payload = recommendation.request_payload if isinstance(recommendation.request_payload, dict) else {}
|
||||
data["recommendation_uuid"] = str(recommendation.uuid)
|
||||
data["crop_id"] = recommendation.crop_id
|
||||
@@ -338,7 +388,22 @@ class RecommendationDetailView(FarmAccessMixin, APIView):
|
||||
data["irrigation_method_name"] = str(request_payload.get("irrigation_method_name") or "")
|
||||
data["status"] = recommendation.status
|
||||
data["status_label"] = recommendation.get_status_display()
|
||||
return Response({"code": 200, "msg": "success", "data": data}, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": data,
|
||||
"meta": build_integration_meta(
|
||||
flow_type="backend_owned_data_with_ai_enrichment",
|
||||
source_type="db",
|
||||
source_service="backend_irrigation",
|
||||
ownership="backend",
|
||||
live=False,
|
||||
cached=False,
|
||||
),
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class WaterStressView(APIView):
|
||||
@@ -392,7 +457,19 @@ class WaterStressView(APIView):
|
||||
|
||||
stress_payload = WaterStressIndexView.extract_stress_payload(adapter_response.data, farm.farm_uuid)
|
||||
return Response(
|
||||
{"code": 200, "msg": "success", "data": stress_payload},
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": stress_payload,
|
||||
"meta": build_integration_meta(
|
||||
flow_type="direct_proxy",
|
||||
source_type="provider",
|
||||
source_service="ai_irrigation_water_stress",
|
||||
ownership="ai",
|
||||
live=True,
|
||||
cached=False,
|
||||
),
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@@ -471,7 +548,19 @@ class PlanFromTextView(FarmAccessMixin, APIView):
|
||||
sync_plan_events(plan, PLAN_TYPE_IRRIGATION)
|
||||
|
||||
return Response(
|
||||
{"code": 200, "msg": response_data.get("msg", "موفق"), "data": response_data.get("data", response_data)},
|
||||
{
|
||||
"code": 200,
|
||||
"msg": response_data.get("msg", "موفق"),
|
||||
"data": response_data.get("data", response_data),
|
||||
"meta": build_integration_meta(
|
||||
flow_type="direct_proxy",
|
||||
source_type="provider",
|
||||
source_service="ai_irrigation_plan_parser",
|
||||
ownership="backend" if final_plan and farm_uuid else "ai",
|
||||
live=True,
|
||||
cached=False,
|
||||
),
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user