This commit is contained in:
2026-05-11 03:27:21 +03:30
parent cf7cbb937c
commit d0e68a1a56
854 changed files with 102985 additions and 76 deletions
@@ -0,0 +1,341 @@
# Fertilization Recommendation API Fields
این فایل فقط فیلدهای API مربوط به `POST /api/fertilization/recommend/` را توضیح می‌دهد.
## Endpoint
`POST /api/fertilization/recommend/`
## ساختار کلی پاسخ
```json
{
"code": 200,
"msg": "success",
"data": {
"primary_recommendation": {},
"nutrient_analysis": {},
"application_guide": {},
"alternative_recommendations": [],
"sections": []
}
}
```
## فیلدهای Request
### `farm_uuid`
- نوع: `string`
- اجباری: بله
- توضیح: شناسه یکتای مزرعه که توصیه برای آن تولید می‌شود.
### `sensor_uuid`
- نوع: `string`
- اجباری: خیر
- توضیح: نام قدیمی برای `farm_uuid`. اگر `farm_uuid` ارسال نشده باشد، این مقدار به جای آن استفاده می‌شود.
### `crop_id`
- نوع: `string`
- اجباری: خیر
- توضیح: شناسه یا نام محصول. اگر `plant_name` ارسال نشده باشد، همین مقدار به عنوان نام گیاه استفاده می‌شود.
### `plant_name`
- نوع: `string`
- اجباری: خیر
- توضیح: نام گیاه یا محصول هدف برای تولید توصیه.
### `growth_stage`
- نوع: `string`
- اجباری: خیر
- توضیح: مرحله رشد گیاه مثل `flowering` یا `fruiting`.
### `query`
- نوع: `string`
- اجباری: خیر
- توضیح: سوال یا درخواست متنی اختیاری برای جهت دادن به توصیه.
## فیلدهای لایه اول Response
### `code`
- نوع: `number`
- توضیح: کد وضعیت پاسخ در قالب استاندارد API پروژه.
### `msg`
- نوع: `string`
- توضیح: پیام وضعیت پاسخ. در حالت موفق معمولاً `success` است.
### `data`
- نوع: `object`
- توضیح: بدنه اصلی توصیه کودهی ساختاریافته.
## فیلدهای `data`
### `primary_recommendation`
- نوع: `object`
- توضیح: پیشنهاد اصلی کودهی که فرانت باید در Hero Card و ماشین حساب مصرف از آن استفاده کند.
### `nutrient_analysis`
- نوع: `object`
- توضیح: تحلیل ساختاریافته عناصر غذایی اصلی و ریزمغذی‌ها.
### `application_guide`
- نوع: `object`
- توضیح: هشدار ایمنی و مراحل اجرای مصرف.
### `alternative_recommendations`
- نوع: `array`
- توضیح: فهرست کودهای جایگزین قابل استفاده در شرایط مشابه.
### `sections`
- نوع: `array`
- توضیح: ساختار legacy برای سازگاری با فرانت یا کلاینت‌های قدیمی.
## فیلدهای `data.primary_recommendation`
### `fertilizer_code`
- نوع: `string`
- توضیح: کد یکتای کود پیشنهادی.
### `fertilizer_name`
- نوع: `string`
- توضیح: نام اصلی کود پیشنهادی برای نمایش.
### `display_title`
- نوع: `string`
- توضیح: عنوان نمایشی آماده برای کارت اصلی.
### `fertilizer_type`
- نوع: `string`
- توضیح: نوع کود مثل `NPK`.
### `npk_ratio`
- نوع: `object`
- توضیح: نسبت NPK به صورت ساختاریافته.
### `application_method`
- نوع: `object`
- توضیح: روش مصرف کود.
### `application_interval`
- نوع: `object`
- توضیح: فاصله زمانی پیشنهادی بین دفعات مصرف.
### `dosage`
- نوع: `object`
- توضیح: مقادیر پایه مصرف که فرانت با آن‌ها مقدار مورد نیاز را حساب می‌کند.
### `reasoning`
- نوع: `string`
- توضیح: توضیح علمی و عملی درباره دلیل انتخاب این توصیه.
### `summary`
- نوع: `string`
- توضیح: خلاصه کوتاه و مناسب نمایش در بخش اصلی فرانت.
## فیلدهای `data.primary_recommendation.npk_ratio`
### `n`
- نوع: `number`
- توضیح: درصد نیتروژن در کود پیشنهادی.
### `p`
- نوع: `number`
- توضیح: درصد فسفر در کود پیشنهادی.
### `k`
- نوع: `number`
- توضیح: درصد پتاسیم در کود پیشنهادی.
### `label`
- نوع: `string`
- توضیح: نمایش متنی نسبت NPK مثل `20-20-20`.
## فیلدهای `data.primary_recommendation.application_method`
### `id`
- نوع: `string`
- توضیح: شناسه استاندارد روش مصرف مثل `fertigation` یا `foliar_fertigation`.
### `label`
- نوع: `string`
- توضیح: متن آماده نمایش برای روش مصرف.
## فیلدهای `data.primary_recommendation.application_interval`
### `value`
- نوع: `number`
- توضیح: فاصله مصرف به صورت عددی.
### `unit`
- نوع: `string`
- توضیح: واحد فاصله مصرف مثل `day`.
### `label`
- نوع: `string`
- توضیح: متن آماده نمایش مثل `هر 14 روز`.
## فیلدهای `data.primary_recommendation.dosage`
### `base_amount_per_hectare`
- نوع: `number`
- توضیح: مقدار پایه مصرف در هر هکتار.
### `base_amount_per_square_meter`
- نوع: `number`
- توضیح: مقدار پایه مصرف در هر متر مربع.
### `unit`
- نوع: `string`
- توضیح: واحد مقدار مصرف مثل `kg`.
### `label`
- نوع: `string`
- توضیح: متن آماده نمایش دوز مثل `65 کیلوگرم در هکتار`.
### `calculation_basis`
- نوع: `string`
- توضیح: مبنای محاسبه دوز؛ معمولاً نام engine یا منبع محاسبه است.
## نکته محاسبه برای فرانت
فرانت باید مقدار نهایی را خودش با استفاده از نسبت پایه محاسبه کند.
فرمول پیشنهادی:
```text
مقدار کل = base_amount_per_square_meter × مساحت مزرعه
```
## فیلدهای `data.nutrient_analysis`
### `macro`
- نوع: `array`
- توضیح: لیست عناصر اصلی شامل N، P و K.
### `micro`
- نوع: `array`
- توضیح: لیست ریزمغذی‌ها مثل آهن، روی، منگنز و غیره. ممکن است خالی باشد.
## فیلدهای هر آیتم در `data.nutrient_analysis.macro[]` و `data.nutrient_analysis.micro[]`
### `key`
- نوع: `string`
- توضیح: کلید استاندارد عنصر مثل `n`، `p`، `k`، `fe` یا `zn`.
### `name`
- نوع: `string`
- توضیح: نام نمایشی عنصر.
### `value`
- نوع: `number`
- توضیح: مقدار عنصر، معمولاً به صورت درصد.
### `unit`
- نوع: `string`
- توضیح: واحد عنصر، معمولاً `percent`.
### `description`
- نوع: `string`
- توضیح: توضیح کوتاه درباره نقش یا اهمیت آن عنصر.
## فیلدهای `data.application_guide`
### `safety_warning`
- نوع: `string`
- توضیح: هشدار ایمنی و اجرایی قبل از مصرف.
### `steps`
- نوع: `array`
- توضیح: مراحل پیشنهادی اجرا.
## فیلدهای هر آیتم در `data.application_guide.steps[]`
### `step_number`
- نوع: `number`
- توضیح: شماره مرحله اجرا.
### `title`
- نوع: `string`
- توضیح: عنوان کوتاه مرحله.
### `description`
- نوع: `string`
- توضیح: توضیح کامل مرحله.
## فیلدهای هر آیتم در `data.alternative_recommendations[]`
### `fertilizer_code`
- نوع: `string`
- توضیح: کد یکتای کود جایگزین.
### `fertilizer_name`
- نوع: `string`
- توضیح: نام کود جایگزین.
### `fertilizer_type`
- نوع: `string`
- توضیح: نوع کود جایگزین.
### `usage_method`
- نوع: `string`
- توضیح: روش مصرف کود جایگزین.
### `description`
- نوع: `string`
- توضیح: توضیح اینکه این جایگزین در چه شرایطی مفید است.
## فیلدهای هر آیتم در `data.sections[]`
### `type`
- نوع: `string`
- توضیح: نوع بخش مثل `recommendation`، `list` یا `warning`.
### `title`
- نوع: `string`
- توضیح: عنوان بخش.
### `icon`
- نوع: `string`
- توضیح: آیکون نمایشی بخش.
### `content`
- نوع: `string`
- توضیح: متن اصلی بخش.
### `items`
- نوع: `array`
- توضیح: لیست آیتم‌های متنی برای بخش‌های لیستی.
### `fertilizerType`
- نوع: `string`
- توضیح: نسخه legacy نوع کود برای نمایش در کلاینت‌های قدیمی.
### `amount`
- نوع: `string`
- توضیح: نسخه legacy مقدار مصرف برای کلاینت‌های قدیمی.
### `applicationMethod`
- نوع: `string`
- توضیح: نسخه legacy روش مصرف.
### `timing`
- نوع: `string`
- توضیح: نسخه legacy زمان مناسب اجرا.
### `validityPeriod`
- نوع: `string`
- توضیح: نسخه legacy مدت اعتبار توصیه.
### `expandableExplanation`
- نوع: `string`
- توضیح: نسخه legacy توضیح کامل‌تر برای نمایش بازشونده.
## فیلدهای حذف شده
فیلدهای زیر دیگر در خروجی اصلی استفاده نمی‌شوند:
- `recommendation_id`
- `crop`
- `growth_stage`
- `total_amount`
- `area` در request
+1
View File
@@ -0,0 +1 @@
+95
View File
@@ -0,0 +1,95 @@
from functools import cached_property
from django.apps import AppConfig
class FertilizationConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "fertilization"
verbose_name = "Fertilization"
tone_file = "config/tones/fertilization_tone.txt"
@cached_property
def optimizer_defaults(self):
return {
"simulation_model": "Wofost81_NWLP_CWB_CNB",
"validity_days": 7,
"default_application_interval_days": 14,
"rain_delay_threshold_mm": 3.0,
"stage_targets": {
"initial": {
"n": 28.0,
"p": 20.0,
"k": 24.0,
"formula": "10-52-10",
"application_method": "استارتر نواری یا همراه آب آبیاری",
"timing": "همزمان با استقرار بوته و در ساعات خنک روز",
"application_interval_days": 10,
},
"vegetative": {
"n": 55.0,
"p": 28.0,
"k": 42.0,
"formula": "20-20-20",
"application_method": "کودآبیاری یا سرک خاکی سبک",
"timing": "صبح زود و ترجیحا قبل از نوبت آبیاری",
"application_interval_days": 12,
},
"flowering": {
"n": 42.0,
"p": 32.0,
"k": 58.0,
"formula": "15-10-30",
"application_method": "کودآبیاری یا محلول پاشی سبک",
"timing": "صبح زود و دور از تنش گرمایی ظهر",
"application_interval_days": 14,
},
"fruiting": {
"n": 35.0,
"p": 24.0,
"k": 68.0,
"formula": "12-12-36",
"application_method": "کودآبیاری با تاکید بر پتاس",
"timing": "صبح زود یا نزدیک غروب",
"application_interval_days": 10,
},
},
"strategy_profiles": [
{
"code": "maintenance",
"label": "تغذیه نگهدارنده",
"multiplier": 0.8,
"focus": "پایه متعادل",
"application_method": "کودآبیاری",
"formula_override": "",
},
{
"code": "balanced",
"label": "تغذیه متعادل",
"multiplier": 1.0,
"focus": "ازت فسفر پتاس متعادل",
"application_method": "کودآبیاری",
"formula_override": "",
},
{
"code": "corrective",
"label": "تغذیه اصلاحی",
"multiplier": 1.2,
"focus": "ازت و پتاس اصلاحی",
"application_method": "کودآبیاری",
"formula_override": "",
},
],
}
def get_optimizer_defaults(self):
return self.optimizer_defaults
@cached_property
def free_text_plan_parser_service(self):
from rag.services.fertilization_plan_parser import FertilizationPlanParserService
return FertilizationPlanParserService()
def get_free_text_plan_parser_service(self):
return self.free_text_plan_parser_service
+151
View File
@@ -0,0 +1,151 @@
from rest_framework import serializers
class FertilizationRecommendRequestSerializer(serializers.Serializer):
"""سریالایزر ورودی برای درخواست توصیه کودهی."""
farm_uuid = serializers.CharField(required=False, help_text="شناسه یکتای مزرعه")
sensor_uuid = serializers.CharField(required=False, help_text="نام قدیمی برای farm_uuid")
crop_id = serializers.CharField(required=False, allow_blank=True, help_text="شناسه یا نام محصول")
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
growth_stage = serializers.CharField(required=False, allow_blank=True, help_text="مرحله رشد گیاه")
query = serializers.CharField(required=False, allow_blank=True, help_text="سوال اختیاری")
def validate(self, attrs):
farm_uuid = attrs.get("farm_uuid") or attrs.get("sensor_uuid")
if not farm_uuid:
raise serializers.ValidationError({"farm_uuid": "farm_uuid الزامی است."})
crop_id = (attrs.get("crop_id") or "").strip()
plant_name = (attrs.get("plant_name") or "").strip()
if crop_id and not plant_name:
attrs["plant_name"] = crop_id
attrs["farm_uuid"] = farm_uuid
return attrs
class NpkRatioSerializer(serializers.Serializer):
n = serializers.FloatField()
p = serializers.FloatField()
k = serializers.FloatField()
label = serializers.CharField()
class FertilizationApplicationMethodSerializer(serializers.Serializer):
id = serializers.CharField()
label = serializers.CharField()
class FertilizationApplicationIntervalSerializer(serializers.Serializer):
value = serializers.IntegerField()
unit = serializers.CharField()
label = serializers.CharField()
class FertilizationDosageSerializer(serializers.Serializer):
base_amount_per_hectare = serializers.FloatField()
base_amount_per_square_meter = serializers.FloatField()
unit = serializers.CharField()
label = serializers.CharField()
calculation_basis = serializers.CharField()
class PrimaryFertilizationRecommendationSerializer(serializers.Serializer):
fertilizer_code = serializers.CharField()
fertilizer_name = serializers.CharField()
display_title = serializers.CharField()
fertilizer_type = serializers.CharField()
npk_ratio = NpkRatioSerializer()
application_method = FertilizationApplicationMethodSerializer()
application_interval = FertilizationApplicationIntervalSerializer()
dosage = FertilizationDosageSerializer()
reasoning = serializers.CharField()
summary = serializers.CharField()
class FertilizationNutrientSerializer(serializers.Serializer):
key = serializers.CharField()
name = serializers.CharField()
value = serializers.FloatField()
unit = serializers.CharField()
description = serializers.CharField(required=False, allow_blank=True)
class FertilizationNutrientAnalysisSerializer(serializers.Serializer):
macro = FertilizationNutrientSerializer(many=True)
micro = FertilizationNutrientSerializer(many=True)
class FertilizationGuideStepSerializer(serializers.Serializer):
step_number = serializers.IntegerField()
title = serializers.CharField()
description = serializers.CharField()
class FertilizationApplicationGuideSerializer(serializers.Serializer):
safety_warning = serializers.CharField()
steps = FertilizationGuideStepSerializer(many=True)
class AlternativeFertilizationRecommendationSerializer(serializers.Serializer):
fertilizer_code = serializers.CharField()
fertilizer_name = serializers.CharField()
fertilizer_type = serializers.CharField()
usage_method = serializers.CharField()
description = serializers.CharField()
class FertilizationSectionSerializer(serializers.Serializer):
type = serializers.CharField()
title = serializers.CharField()
icon = serializers.CharField(required=False, allow_blank=True)
content = serializers.CharField(required=False, allow_blank=True)
items = serializers.ListField(child=serializers.CharField(), required=False)
fertilizerType = serializers.CharField(required=False, allow_blank=True)
amount = serializers.CharField(required=False, allow_blank=True)
applicationMethod = serializers.CharField(required=False, allow_blank=True)
timing = serializers.CharField(required=False, allow_blank=True)
validityPeriod = serializers.CharField(required=False, allow_blank=True)
expandableExplanation = serializers.CharField(required=False, allow_blank=True)
class FertilizationRecommendationResponseDataSerializer(serializers.Serializer):
primary_recommendation = PrimaryFertilizationRecommendationSerializer()
nutrient_analysis = FertilizationNutrientAnalysisSerializer()
application_guide = FertilizationApplicationGuideSerializer()
alternative_recommendations = AlternativeFertilizationRecommendationSerializer(many=True)
sections = FertilizationSectionSerializer(many=True, required=False)
class FertilizationPlanParserRequestSerializer(serializers.Serializer):
message = serializers.CharField(required=False, allow_blank=True, help_text="توضیح آزاد کاربر درباره برنامه کودهی")
answers = serializers.JSONField(required=False, help_text="پاسخ های تکمیلی کاربر به سوالات مرحله قبل")
partial_plan = serializers.JSONField(required=False, help_text="داده استخراج شده مرحله قبل برای ادامه تکمیل")
farm_uuid = serializers.CharField(required=False, allow_blank=True, help_text="شناسه مزرعه برای غنی سازی context")
def validate(self, attrs):
message = (attrs.get("message") or "").strip()
answers = attrs.get("answers")
partial_plan = attrs.get("partial_plan")
if not message and not isinstance(answers, dict) and not isinstance(partial_plan, dict):
raise serializers.ValidationError(
"حداقل یکی از message، answers یا partial_plan باید ارسال شود."
)
return attrs
class PlanClarificationQuestionSerializer(serializers.Serializer):
id = serializers.CharField()
field = serializers.CharField()
question = serializers.CharField()
rationale = serializers.CharField(required=False, allow_blank=True)
class FertilizationPlanParserResponseSerializer(serializers.Serializer):
status = serializers.CharField()
status_fa = serializers.CharField()
summary = serializers.CharField()
missing_fields = serializers.ListField(child=serializers.CharField())
questions = PlanClarificationQuestionSerializer(many=True)
collected_data = serializers.JSONField()
final_plan = serializers.JSONField(required=False, allow_null=True)
+8
View File
@@ -0,0 +1,8 @@
from django.urls import path
from .views import FertilizationPlanParserView, FertilizationRecommendView
urlpatterns = [
path("recommend/", FertilizationRecommendView.as_view(), name="fertilization-recommend"),
path("plan-from-text/", FertilizationPlanParserView.as_view(), name="fertilization-plan-from-text"),
]
+234
View File
@@ -0,0 +1,234 @@
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 .serializers import (
FertilizationPlanParserRequestSerializer,
FertilizationPlanParserResponseSerializer,
FertilizationRecommendationResponseDataSerializer,
FertilizationRecommendRequestSerializer,
)
FertilizationValidationErrorSerializer = build_envelope_serializer(
"FertilizationValidationErrorSerializer",
data_required=False,
allow_null=True,
)
FertilizationResponseSerializer = build_envelope_serializer(
"FertilizationResponseSerializer",
data_schema=FertilizationRecommendationResponseDataSerializer,
)
FertilizationPlanParserEnvelopeSerializer = build_envelope_serializer(
"FertilizationPlanParserEnvelopeSerializer",
data_schema=FertilizationPlanParserResponseSerializer,
)
class FertilizationRecommendView(APIView):
"""
توصیه کودهی ساختاریافته با ترکیب RAG و optimizer شبیه سازی.
"""
@extend_schema(
tags=["Fertilization Recommendation"],
summary="درخواست توصیه کودهی ساختاریافته",
description=(
"داده های مزرعه، گیاه و مرحله رشد را دریافت می کند و "
"خروجی نهایی بهینه شده با ترکیب RAG و optimizer مبتنی بر crop_simulation/PCSE را برمی گرداند."
),
request=FertilizationRecommendRequestSerializer,
responses={
200: build_response(
FertilizationResponseSerializer,
"توصیه کودهی ساختاریافته با موفقیت تولید شد.",
),
400: build_response(
FertilizationValidationErrorSerializer,
"پارامتر ورودی نامعتبر است.",
),
500: build_response(
FertilizationValidationErrorSerializer,
"خطا در تولید توصیه کودهی.",
),
},
examples=[
OpenApiExample(
"نمونه درخواست",
value={
"farm_uuid": "11111111-1111-1111-1111-111111111111",
"crop_id": "wheat",
"growth_stage": "flowering",
},
request_only=True,
),
OpenApiExample(
"نمونه پاسخ",
value={
"code": 200,
"msg": "success",
"data": {
"primary_recommendation": {
"fertilizer_code": "15-10-30",
"fertilizer_name": "کود کامل 15-10-30",
"display_title": "کود کامل 15-10-30",
"fertilizer_type": "NPK",
"npk_ratio": {"n": 15, "p": 10, "k": 30, "label": "15-10-30"},
"application_method": {
"id": "foliar_fertigation",
"label": "کودآبیاری یا محلول پاشی سبک",
},
"application_interval": {"value": 14, "unit": "day", "label": "هر 14 روز"},
"dosage": {
"base_amount_per_hectare": 65,
"base_amount_per_square_meter": 0.0065,
"unit": "kg",
"label": "65 کیلوگرم در هکتار",
"calculation_basis": "crop_simulation_heuristic",
},
"reasoning": "این ترکیب برای مرحله گلدهی و توازن نیازهای تغذیه ای مناسب است.",
"summary": "برای پشتیبانی از گلدهی و کاهش تنش تغذیه ای پیشنهاد می شود.",
},
"nutrient_analysis": {
"macro": [
{
"key": "n",
"name": "نیتروژن (N)",
"value": 15,
"unit": "percent",
"description": "نیتروژن برای حفظ رشد رویشی مهم است.",
}
],
"micro": [],
},
"application_guide": {
"safety_warning": "در ساعات خنک مصرف شود و از اختلاط ناسازگار خودداری کنید.",
"steps": [
{"step_number": 1, "title": "آماده سازی", "description": "دوز را آماده کنید."},
{"step_number": 2, "title": "تزریق یا پخش", "description": "طبق روش مصرف اجرا کنید."},
{"step_number": 3, "title": "پایش", "description": "پاسخ مزرعه را بررسی کنید."},
],
},
"alternative_recommendations": [],
},
},
response_only=True,
),
],
)
def post(self, request):
from rag.services.fertilization import get_fertilization_recommendation
serializer = FertilizationRecommendRequestSerializer(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_fertilization_recommendation(
farm_uuid=validated["farm_uuid"],
plant_name=validated.get("plant_name"),
crop_id=validated.get("crop_id"),
growth_stage=validated.get("growth_stage"),
query=validated.get("query"),
)
except Exception as exc:
return Response(
{"code": 500, "msg": f"خطا در تولید توصیه کودهی: {exc}", "data": None},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
final_result = result.get("data") if isinstance(result, dict) else None
if not isinstance(final_result, dict):
final_result = {"sections": result.get("sections", [])} if isinstance(result, dict) else {}
return Response(
{"code": 200, "msg": "success", "data": final_result},
status=status.HTTP_200_OK,
)
class FertilizationPlanParserView(APIView):
@extend_schema(
tags=["Fertilization Recommendation"],
summary="استخراج برنامه کودهی از متن آزاد",
description=(
"توضیح متنی کاربر درباره برنامه کودهی را می گیرد و آن را به JSON ساختاریافته تبدیل می کند. "
"اگر اطلاعات کافی نباشد، سوالات تکمیلی لازم را برمی گرداند تا در درخواست بعدی پاسخ داده شوند."
),
request=FertilizationPlanParserRequestSerializer,
responses={
200: build_response(
FertilizationPlanParserEnvelopeSerializer,
"نتیجه استخراج یا سوالات تکمیلی برنامه کودهی.",
),
400: build_response(
FertilizationValidationErrorSerializer,
"داده ورودی نامعتبر است.",
),
500: build_response(
FertilizationValidationErrorSerializer,
"خطا در پردازش برنامه کودهی.",
),
},
examples=[
OpenApiExample(
"نمونه درخواست کامل",
value={
"message": "برای گندم در مرحله پنجه زنی هر 12 روز یک بار 20-20-20 به مقدار 35 کیلوگرم در هکتار از طریق کودآبیاری می دهم.",
"farm_uuid": "11111111-1111-1111-1111-111111111111",
},
request_only=True,
),
OpenApiExample(
"نمونه درخواست تکمیلی",
value={
"partial_plan": {
"crop_name": "گندم",
"applications": [{"fertilizer_name": "20-20-20"}],
},
"answers": {
"amount": "35 کیلوگرم در هکتار",
"timing": "هر 12 روز یک بار",
},
},
request_only=True,
),
],
)
def post(self, request):
serializer = FertilizationPlanParserRequestSerializer(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("fertilization").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,
)