UPDATE
This commit is contained in:
@@ -173,19 +173,20 @@ def _merge_fertilization_response(
|
||||
|
||||
|
||||
def get_fertilization_recommendation(
|
||||
sensor_uuid: str,
|
||||
farm_uuid: str | None = None,
|
||||
plant_name: str | None = None,
|
||||
growth_stage: str | None = None,
|
||||
query: str | None = None,
|
||||
config: RAGConfig | None = None,
|
||||
limit: int = 8,
|
||||
sensor_uuid: str | None = None,
|
||||
) -> dict:
|
||||
"""
|
||||
توصیه کودهی برای یک سنسور (کاربر).
|
||||
توصیه کودهی برای یک مزرعه.
|
||||
از RAG با پایگاه دانش fertilization استفاده میکند.
|
||||
|
||||
Args:
|
||||
sensor_uuid: شناسه سنسور کاربر
|
||||
farm_uuid: شناسه مزرعه
|
||||
plant_name: نام گیاه (برای بارگذاری مشخصات از جدول Plant)
|
||||
growth_stage: مرحله رشد گیاه
|
||||
query: سوال اختیاری
|
||||
@@ -193,7 +194,7 @@ def get_fertilization_recommendation(
|
||||
limit: تعداد چانکهای بازیابیشده
|
||||
|
||||
Returns:
|
||||
dict با کلیدهای fertilizer_needed, fertilizer_type, amount_kg_per_hectare, reason, npk_status, raw_response
|
||||
dict ساختاریافته برای توصیه کودهی
|
||||
"""
|
||||
cfg = config or load_rag_config()
|
||||
service = get_service_config(SERVICE_ID, cfg)
|
||||
@@ -209,12 +210,16 @@ def get_fertilization_recommendation(
|
||||
client = get_chat_client(service_cfg)
|
||||
model = service.llm.model
|
||||
|
||||
resolved_farm_uuid = str(farm_uuid or sensor_uuid or "").strip()
|
||||
if not resolved_farm_uuid:
|
||||
raise ValueError("farm_uuid is required.")
|
||||
|
||||
user_query = query or "توصیه کودهی برای مزرعه من چیست؟"
|
||||
|
||||
sensor = (
|
||||
SensorData.objects.select_related("center_location")
|
||||
.prefetch_related("plants")
|
||||
.filter(farm_uuid=sensor_uuid)
|
||||
.filter(farm_uuid=resolved_farm_uuid)
|
||||
.first()
|
||||
)
|
||||
resolved_plant_name = plant_name
|
||||
@@ -246,7 +251,7 @@ def get_fertilization_recommendation(
|
||||
)
|
||||
|
||||
context = build_rag_context(
|
||||
user_query, sensor_uuid, config=cfg, limit=limit, kb_name=KB_NAME, service_id=SERVICE_ID,
|
||||
user_query, resolved_farm_uuid, config=cfg, limit=limit, kb_name=KB_NAME, service_id=SERVICE_ID,
|
||||
)
|
||||
|
||||
extra_parts: list[str] = []
|
||||
@@ -276,7 +281,7 @@ def get_fertilization_recommendation(
|
||||
{"role": "user", "content": user_query},
|
||||
]
|
||||
audit_log = _create_audit_log(
|
||||
farm_uuid=sensor_uuid,
|
||||
farm_uuid=resolved_farm_uuid,
|
||||
service_id=SERVICE_ID,
|
||||
model=model,
|
||||
query=user_query,
|
||||
@@ -291,7 +296,7 @@ def get_fertilization_recommendation(
|
||||
)
|
||||
raw = response.choices[0].message.content.strip()
|
||||
except Exception as exc:
|
||||
logger.error("Fertilization recommendation error for %s: %s", sensor_uuid, exc)
|
||||
logger.error("Fertilization recommendation error for %s: %s", resolved_farm_uuid, exc)
|
||||
result = _build_fertilization_fallback(optimized_result=optimized_result)
|
||||
result["error"] = f"خطا در دریافت توصیه: {exc}"
|
||||
result["raw_response"] = None
|
||||
|
||||
@@ -219,20 +219,21 @@ def _persist_irrigation_method_on_farm(
|
||||
|
||||
|
||||
def get_irrigation_recommendation(
|
||||
sensor_uuid: str,
|
||||
farm_uuid: str | None = None,
|
||||
plant_name: str | None = None,
|
||||
growth_stage: str | None = None,
|
||||
irrigation_method_name: str | None = None,
|
||||
query: str | None = None,
|
||||
config: RAGConfig | None = None,
|
||||
limit: int = 8,
|
||||
sensor_uuid: str | None = None,
|
||||
) -> dict:
|
||||
"""
|
||||
توصیه آبیاری برای یک سنسور (کاربر).
|
||||
توصیه آبیاری برای یک مزرعه.
|
||||
از RAG با پایگاه دانش irrigation استفاده میکند.
|
||||
|
||||
Args:
|
||||
sensor_uuid: شناسه سنسور کاربر
|
||||
farm_uuid: شناسه مزرعه
|
||||
plant_name: نام گیاه (برای بارگذاری مشخصات از جدول Plant)
|
||||
growth_stage: مرحله رشد گیاه
|
||||
irrigation_method_name: نام روش آبیاری (برای بارگذاری از جدول IrrigationMethod)
|
||||
@@ -241,7 +242,7 @@ def get_irrigation_recommendation(
|
||||
limit: تعداد چانکهای بازیابیشده
|
||||
|
||||
Returns:
|
||||
dict با کلیدهای irrigation_needed, amount_mm, reason, next_check_date, raw_response
|
||||
dict ساختاریافته برای توصیه آبیاری
|
||||
"""
|
||||
cfg = config or load_rag_config()
|
||||
service = get_service_config(SERVICE_ID, cfg)
|
||||
@@ -257,12 +258,16 @@ def get_irrigation_recommendation(
|
||||
client = get_chat_client(service_cfg)
|
||||
model = service.llm.model
|
||||
|
||||
resolved_farm_uuid = str(farm_uuid or sensor_uuid or "").strip()
|
||||
if not resolved_farm_uuid:
|
||||
raise ValueError("farm_uuid is required.")
|
||||
|
||||
user_query = query or "توصیه آبیاری برای مزرعه من چیست؟"
|
||||
|
||||
sensor = (
|
||||
SensorData.objects.select_related("center_location", "irrigation_method")
|
||||
.prefetch_related("plants")
|
||||
.filter(farm_uuid=sensor_uuid)
|
||||
.filter(farm_uuid=resolved_farm_uuid)
|
||||
.first()
|
||||
)
|
||||
irrigation_method = _resolve_irrigation_method(sensor, irrigation_method_name)
|
||||
@@ -309,7 +314,7 @@ def get_irrigation_recommendation(
|
||||
)
|
||||
|
||||
context = build_rag_context(
|
||||
user_query, sensor_uuid, config=cfg, limit=limit, kb_name=KB_NAME, service_id=SERVICE_ID,
|
||||
user_query, resolved_farm_uuid, config=cfg, limit=limit, kb_name=KB_NAME, service_id=SERVICE_ID,
|
||||
)
|
||||
|
||||
extra_parts: list[str] = []
|
||||
@@ -360,7 +365,7 @@ def get_irrigation_recommendation(
|
||||
{"role": "user", "content": user_query},
|
||||
]
|
||||
audit_log = _create_audit_log(
|
||||
farm_uuid=sensor_uuid,
|
||||
farm_uuid=resolved_farm_uuid,
|
||||
service_id=SERVICE_ID,
|
||||
model=model,
|
||||
query=user_query,
|
||||
@@ -375,7 +380,7 @@ def get_irrigation_recommendation(
|
||||
)
|
||||
raw = response.choices[0].message.content.strip()
|
||||
except Exception as exc:
|
||||
logger.error("Irrigation recommendation error for %s: %s", sensor_uuid, exc)
|
||||
logger.error("Irrigation recommendation error for %s: %s", resolved_farm_uuid, exc)
|
||||
result = _build_irrigation_fallback(
|
||||
optimized_result=optimized_result,
|
||||
daily_water_needs=daily_water_needs,
|
||||
|
||||
+4
-4
@@ -20,7 +20,7 @@ def rag_ingest_task(recreate: bool = True):
|
||||
@app.task(bind=True)
|
||||
def irrigation_recommendation_task(
|
||||
self,
|
||||
sensor_uuid: str,
|
||||
farm_uuid: str,
|
||||
plant_name: str | None = None,
|
||||
growth_stage: str | None = None,
|
||||
irrigation_method_name: str | None = None,
|
||||
@@ -38,7 +38,7 @@ def irrigation_recommendation_task(
|
||||
meta={"message": "در حال پردازش توصیه آبیاری..."},
|
||||
)
|
||||
result = get_irrigation_recommendation(
|
||||
sensor_uuid=sensor_uuid,
|
||||
farm_uuid=farm_uuid,
|
||||
plant_name=plant_name,
|
||||
growth_stage=growth_stage,
|
||||
irrigation_method_name=irrigation_method_name,
|
||||
@@ -51,7 +51,7 @@ def irrigation_recommendation_task(
|
||||
@app.task(bind=True)
|
||||
def fertilization_recommendation_task(
|
||||
self,
|
||||
sensor_uuid: str,
|
||||
farm_uuid: str,
|
||||
plant_name: str | None = None,
|
||||
growth_stage: str | None = None,
|
||||
query: str | None = None,
|
||||
@@ -68,7 +68,7 @@ def fertilization_recommendation_task(
|
||||
meta={"message": "در حال پردازش توصیه کودهی..."},
|
||||
)
|
||||
result = get_fertilization_recommendation(
|
||||
sensor_uuid=sensor_uuid,
|
||||
farm_uuid=farm_uuid,
|
||||
plant_name=plant_name,
|
||||
growth_stage=growth_stage,
|
||||
query=query,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, override_settings
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF="config.test_urls")
|
||||
class RagRecommendationApiTests(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
@@ -21,7 +22,7 @@ class RagRecommendationApiTests(TestCase):
|
||||
response = self.client.post(
|
||||
"/api/rag/recommend/irrigation/",
|
||||
data={
|
||||
"sensor_uuid": "sensor-123",
|
||||
"farm_uuid": "sensor-123",
|
||||
"plant_name": "گوجهفرنگی",
|
||||
"growth_stage": "میوهدهی",
|
||||
"irrigation_method_name": "قطرهای",
|
||||
@@ -32,7 +33,7 @@ class RagRecommendationApiTests(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json()["data"]["plan"]["frequencyPerWeek"], 3)
|
||||
mock_get_irrigation_recommendation.assert_called_once_with(
|
||||
sensor_uuid="sensor-123",
|
||||
farm_uuid="sensor-123",
|
||||
plant_name="گوجهفرنگی",
|
||||
growth_stage="میوهدهی",
|
||||
irrigation_method_name="قطرهای",
|
||||
@@ -52,7 +53,7 @@ class RagRecommendationApiTests(TestCase):
|
||||
response = self.client.post(
|
||||
"/api/rag/recommend/fertilization/",
|
||||
data={
|
||||
"sensor_uuid": "sensor-456",
|
||||
"farm_uuid": "sensor-456",
|
||||
"plant_name": "گندم",
|
||||
"growth_stage": "رویشی",
|
||||
},
|
||||
@@ -62,7 +63,7 @@ class RagRecommendationApiTests(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.json()["data"]["plan"]["npkRatio"], "20-20-20")
|
||||
mock_get_fertilization_recommendation.assert_called_once_with(
|
||||
sensor_uuid="sensor-456",
|
||||
farm_uuid="sensor-456",
|
||||
plant_name="گندم",
|
||||
growth_stage="رویشی",
|
||||
query=None,
|
||||
|
||||
@@ -130,7 +130,7 @@ class RecommendationServiceDefaultsTests(TestCase):
|
||||
mock_get_chat_client.return_value.chat.completions.create.return_value = mock_response
|
||||
|
||||
result = get_irrigation_recommendation(
|
||||
sensor_uuid=str(self.farm_uuid),
|
||||
farm_uuid=str(self.farm_uuid),
|
||||
growth_stage="میوهدهی",
|
||||
)
|
||||
|
||||
@@ -176,7 +176,7 @@ class RecommendationServiceDefaultsTests(TestCase):
|
||||
mock_get_chat_client.return_value.chat.completions.create.return_value = mock_response
|
||||
|
||||
result = get_irrigation_recommendation(
|
||||
sensor_uuid=str(self.farm_uuid),
|
||||
farm_uuid=str(self.farm_uuid),
|
||||
growth_stage="میوهدهی",
|
||||
irrigation_method_name="بارانی",
|
||||
)
|
||||
@@ -205,7 +205,7 @@ class RecommendationServiceDefaultsTests(TestCase):
|
||||
mock_get_chat_client.return_value.chat.completions.create.return_value = mock_response
|
||||
|
||||
result = get_fertilization_recommendation(
|
||||
sensor_uuid=str(self.farm_uuid),
|
||||
farm_uuid=str(self.farm_uuid),
|
||||
growth_stage="رویشی",
|
||||
)
|
||||
|
||||
@@ -232,7 +232,7 @@ class RecommendationServiceDefaultsTests(TestCase):
|
||||
mock_get_chat_client.return_value.chat.completions.create.return_value = mock_response
|
||||
|
||||
result = get_fertilization_recommendation(
|
||||
sensor_uuid=str(self.farm_uuid),
|
||||
farm_uuid=str(self.farm_uuid),
|
||||
growth_stage="رویشی",
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -8,6 +8,6 @@ from .views import (
|
||||
|
||||
urlpatterns = [
|
||||
path("chat/", ChatView.as_view()),
|
||||
# path("recommend/irrigation/", IrrigationRecommendationView.as_view(), name="recommend-irrigation"),
|
||||
# path("recommend/fertilization/", FertilizationRecommendationView.as_view(), name="recommend-fertilization"),
|
||||
path("recommend/irrigation/", IrrigationRecommendationView.as_view(), name="recommend-irrigation"),
|
||||
path("recommend/fertilization/", FertilizationRecommendationView.as_view(), name="recommend-fertilization"),
|
||||
]
|
||||
|
||||
+18
-16
@@ -148,7 +148,7 @@ class ChatView(APIView):
|
||||
class IrrigationRecommendationView(APIView):
|
||||
"""
|
||||
توصیه آبیاری به صورت مستقیم.
|
||||
POST با sensor_uuid، plant_name، growth_stage، irrigation_method_name.
|
||||
POST با farm_uuid، plant_name، growth_stage، irrigation_method_name.
|
||||
نتیجه همان لحظه برگشت داده میشود.
|
||||
"""
|
||||
|
||||
@@ -162,7 +162,8 @@ class IrrigationRecommendationView(APIView):
|
||||
request=inline_serializer(
|
||||
name="IrrigationRecommendationRequest",
|
||||
fields={
|
||||
"sensor_uuid": drf_serializers.CharField(help_text="شناسه یکتای سنسور (اجباری)"),
|
||||
"farm_uuid": drf_serializers.CharField(help_text="شناسه یکتای مزرعه (اجباری)"),
|
||||
"sensor_uuid": drf_serializers.CharField(required=False, help_text="نام قدیمی برای farm_uuid"),
|
||||
"plant_name": drf_serializers.CharField(required=False, help_text="نام گیاه"),
|
||||
"growth_stage": drf_serializers.CharField(required=False, help_text="مرحله رشد گیاه"),
|
||||
"irrigation_method_name": drf_serializers.CharField(required=False, help_text="نام روش آبیاری"),
|
||||
@@ -187,7 +188,7 @@ class IrrigationRecommendationView(APIView):
|
||||
OpenApiExample(
|
||||
"نمونه درخواست",
|
||||
value={
|
||||
"sensor_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"plant_name": "گوجهفرنگی",
|
||||
"growth_stage": "میوهدهی",
|
||||
"irrigation_method_name": "آبیاری قطرهای",
|
||||
@@ -199,23 +200,23 @@ class IrrigationRecommendationView(APIView):
|
||||
def post(self, request: Request):
|
||||
from rag.services.irrigation import get_irrigation_recommendation
|
||||
|
||||
sensor_uuid = request.data.get("sensor_uuid")
|
||||
if not sensor_uuid:
|
||||
farm_uuid = request.data.get("farm_uuid") or request.data.get("sensor_uuid")
|
||||
if not farm_uuid:
|
||||
return Response(
|
||||
{"code": 400, "msg": "پارامتر sensor_uuid الزامی است.", "data": None},
|
||||
{"code": 400, "msg": "پارامتر farm_uuid الزامی است.", "data": None},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
try:
|
||||
result = get_irrigation_recommendation(
|
||||
sensor_uuid=str(sensor_uuid),
|
||||
farm_uuid=str(farm_uuid),
|
||||
plant_name=request.data.get("plant_name"),
|
||||
growth_stage=request.data.get("growth_stage"),
|
||||
irrigation_method_name=request.data.get("irrigation_method_name"),
|
||||
query=request.data.get("query"),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Direct irrigation recommendation failed for sensor %s", sensor_uuid)
|
||||
logger.exception("Direct irrigation recommendation failed for farm %s", farm_uuid)
|
||||
return Response(
|
||||
{"code": 500, "msg": "خطا در تولید توصیه آبیاری.", "data": None},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -230,7 +231,7 @@ class IrrigationRecommendationView(APIView):
|
||||
class FertilizationRecommendationView(APIView):
|
||||
"""
|
||||
توصیه کودهی به صورت مستقیم.
|
||||
POST با sensor_uuid، plant_name، growth_stage.
|
||||
POST با farm_uuid، plant_name، growth_stage.
|
||||
نتیجه همان لحظه برگشت داده میشود.
|
||||
"""
|
||||
|
||||
@@ -244,7 +245,8 @@ class FertilizationRecommendationView(APIView):
|
||||
request=inline_serializer(
|
||||
name="FertilizationRecommendationRequest",
|
||||
fields={
|
||||
"sensor_uuid": drf_serializers.CharField(help_text="شناسه یکتای سنسور (اجباری)"),
|
||||
"farm_uuid": drf_serializers.CharField(help_text="شناسه یکتای مزرعه (اجباری)"),
|
||||
"sensor_uuid": drf_serializers.CharField(required=False, help_text="نام قدیمی برای farm_uuid"),
|
||||
"plant_name": drf_serializers.CharField(required=False, help_text="نام گیاه"),
|
||||
"growth_stage": drf_serializers.CharField(required=False, help_text="مرحله رشد گیاه"),
|
||||
"query": drf_serializers.CharField(required=False, help_text="سوال اختیاری"),
|
||||
@@ -268,7 +270,7 @@ class FertilizationRecommendationView(APIView):
|
||||
OpenApiExample(
|
||||
"نمونه درخواست",
|
||||
value={
|
||||
"sensor_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"plant_name": "گوجهفرنگی",
|
||||
"growth_stage": "رویشی",
|
||||
},
|
||||
@@ -279,22 +281,22 @@ class FertilizationRecommendationView(APIView):
|
||||
def post(self, request: Request):
|
||||
from rag.services.fertilization import get_fertilization_recommendation
|
||||
|
||||
sensor_uuid = request.data.get("sensor_uuid")
|
||||
if not sensor_uuid:
|
||||
farm_uuid = request.data.get("farm_uuid") or request.data.get("sensor_uuid")
|
||||
if not farm_uuid:
|
||||
return Response(
|
||||
{"code": 400, "msg": "پارامتر sensor_uuid الزامی است.", "data": None},
|
||||
{"code": 400, "msg": "پارامتر farm_uuid الزامی است.", "data": None},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
try:
|
||||
result = get_fertilization_recommendation(
|
||||
sensor_uuid=str(sensor_uuid),
|
||||
farm_uuid=str(farm_uuid),
|
||||
plant_name=request.data.get("plant_name"),
|
||||
growth_stage=request.data.get("growth_stage"),
|
||||
query=request.data.get("query"),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Direct fertilization recommendation failed for sensor %s", sensor_uuid)
|
||||
logger.exception("Direct fertilization recommendation failed for farm %s", farm_uuid)
|
||||
return Response(
|
||||
{"code": 500, "msg": "خطا در تولید توصیه کودهی.", "data": None},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
|
||||
Reference in New Issue
Block a user