UPDATE
This commit is contained in:
@@ -470,7 +470,12 @@ def _run_projection_engine(context: GrowthSimulationContext) -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _run_simulation(context: GrowthSimulationContext) -> tuple[dict[str, Any], int | None, str | None]:
|
def _run_simulation(
|
||||||
|
context: GrowthSimulationContext,
|
||||||
|
*,
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
|
) -> tuple[dict[str, Any], int | None, str | None]:
|
||||||
try:
|
try:
|
||||||
response = CropSimulationService().run_single_simulation(
|
response = CropSimulationService().run_single_simulation(
|
||||||
farm_uuid=context.farm_uuid,
|
farm_uuid=context.farm_uuid,
|
||||||
@@ -480,6 +485,8 @@ def _run_simulation(context: GrowthSimulationContext) -> tuple[dict[str, Any], i
|
|||||||
crop_parameters=context.crop_parameters,
|
crop_parameters=context.crop_parameters,
|
||||||
agromanagement=context.agromanagement,
|
agromanagement=context.agromanagement,
|
||||||
site_parameters=context.site_parameters,
|
site_parameters=context.site_parameters,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
name=f"growth:{context.plant_name}",
|
name=f"growth:{context.plant_name}",
|
||||||
)
|
)
|
||||||
return response["result"], response.get("scenario_id"), None
|
return response["result"], response.get("scenario_id"), None
|
||||||
@@ -619,7 +626,11 @@ def run_growth_simulation(payload: dict[str, Any], progress_callback=None) -> di
|
|||||||
meta={"current": 1, "total": 3, "message": "simulation input resolved"},
|
meta={"current": 1, "total": 3, "message": "simulation input resolved"},
|
||||||
)
|
)
|
||||||
|
|
||||||
simulation_result, scenario_id, simulation_error = _run_simulation(context)
|
simulation_result, scenario_id, simulation_error = _run_simulation(
|
||||||
|
context,
|
||||||
|
irrigation_recommendation=payload.get("irrigation_recommendation"),
|
||||||
|
fertilization_recommendation=payload.get("fertilization_recommendation"),
|
||||||
|
)
|
||||||
if progress_callback is not None:
|
if progress_callback is not None:
|
||||||
progress_callback(
|
progress_callback(
|
||||||
state="PROGRESS",
|
state="PROGRESS",
|
||||||
@@ -751,7 +762,14 @@ def _build_current_farm_chart_payload(
|
|||||||
class CurrentFarmChartSimulator:
|
class CurrentFarmChartSimulator:
|
||||||
"""سازنده chart وضعیت فعلی مزرعه برای خروجی مستقل از dashboard."""
|
"""سازنده chart وضعیت فعلی مزرعه برای خروجی مستقل از dashboard."""
|
||||||
|
|
||||||
def simulate(self, *, farm_uuid: str, plant_name: str | None = None) -> dict[str, Any]:
|
def simulate(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
farm_uuid: str,
|
||||||
|
plant_name: str | None = None,
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
if not farm_uuid:
|
if not farm_uuid:
|
||||||
raise GrowthSimulationError("ارسال farm_uuid الزامی است.")
|
raise GrowthSimulationError("ارسال farm_uuid الزامی است.")
|
||||||
|
|
||||||
@@ -777,7 +795,11 @@ class CurrentFarmChartSimulator:
|
|||||||
"page_size": DEFAULT_PAGE_SIZE,
|
"page_size": DEFAULT_PAGE_SIZE,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
simulation_result, scenario_id, simulation_warning = _run_simulation(context)
|
simulation_result, scenario_id, simulation_warning = _run_simulation(
|
||||||
|
context,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
|
)
|
||||||
return _build_current_farm_chart_payload(
|
return _build_current_farm_chart_payload(
|
||||||
context,
|
context,
|
||||||
simulation_result,
|
simulation_result,
|
||||||
|
|||||||
@@ -46,7 +46,13 @@ def _harvest_description(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_harvest_prediction_payload(*, farm_uuid: str, plant_name: str | None = None) -> dict[str, Any]:
|
def build_harvest_prediction_payload(
|
||||||
|
*,
|
||||||
|
farm_uuid: str,
|
||||||
|
plant_name: str | None = None,
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
resolved_plant_name = plant_name
|
resolved_plant_name = plant_name
|
||||||
if not resolved_plant_name:
|
if not resolved_plant_name:
|
||||||
sensor = SensorData.objects.prefetch_related("plants").filter(farm_uuid=farm_uuid).first()
|
sensor = SensorData.objects.prefetch_related("plants").filter(farm_uuid=farm_uuid).first()
|
||||||
@@ -65,7 +71,11 @@ def build_harvest_prediction_payload(*, farm_uuid: str, plant_name: str | None =
|
|||||||
"page_size": DEFAULT_PAGE_SIZE,
|
"page_size": DEFAULT_PAGE_SIZE,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
simulation_result, scenario_id, simulation_warning = _run_simulation(context)
|
simulation_result, scenario_id, simulation_warning = _run_simulation(
|
||||||
|
context,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
|
)
|
||||||
daily_output = simulation_result.get("daily_output") or []
|
daily_output = simulation_result.get("daily_output") or []
|
||||||
if not daily_output:
|
if not daily_output:
|
||||||
raise GrowthSimulationError("هیچ خروجی شبیه سازی در دسترس نیست.")
|
raise GrowthSimulationError("هیچ خروجی شبیه سازی در دسترس نیست.")
|
||||||
@@ -146,5 +156,17 @@ def build_harvest_prediction_payload(*, farm_uuid: str, plant_name: str | None =
|
|||||||
|
|
||||||
|
|
||||||
class HarvestPredictionService:
|
class HarvestPredictionService:
|
||||||
def get_harvest_prediction(self, *, farm_uuid: str, plant_name: str | None = None) -> dict[str, Any]:
|
def get_harvest_prediction(
|
||||||
return build_harvest_prediction_payload(farm_uuid=farm_uuid, plant_name=plant_name)
|
self,
|
||||||
|
*,
|
||||||
|
farm_uuid: str,
|
||||||
|
plant_name: str | None = None,
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return build_harvest_prediction_payload(
|
||||||
|
farm_uuid=farm_uuid,
|
||||||
|
plant_name=plant_name,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class QueryJSONField(serializers.JSONField):
|
||||||
|
def to_internal_value(self, data):
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.strip()
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
data = json.loads(data)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise serializers.ValidationError("فرمت JSON نامعتبر است.") from exc
|
||||||
|
return super().to_internal_value(data)
|
||||||
|
|
||||||
|
|
||||||
class GrowthSimulationRequestSerializer(serializers.Serializer):
|
class GrowthSimulationRequestSerializer(serializers.Serializer):
|
||||||
plant_name = serializers.CharField(help_text="نام گیاه")
|
plant_name = serializers.CharField(help_text="نام گیاه")
|
||||||
dynamic_parameters = serializers.ListField(
|
dynamic_parameters = serializers.ListField(
|
||||||
@@ -16,6 +31,8 @@ class GrowthSimulationRequestSerializer(serializers.Serializer):
|
|||||||
site_parameters = serializers.JSONField(required=False)
|
site_parameters = serializers.JSONField(required=False)
|
||||||
crop_parameters = serializers.JSONField(required=False)
|
crop_parameters = serializers.JSONField(required=False)
|
||||||
agromanagement = serializers.JSONField(required=False)
|
agromanagement = serializers.JSONField(required=False)
|
||||||
|
irrigation_recommendation = serializers.JSONField(required=False)
|
||||||
|
fertilization_recommendation = serializers.JSONField(required=False)
|
||||||
page_size = serializers.IntegerField(required=False, min_value=1, max_value=50)
|
page_size = serializers.IntegerField(required=False, min_value=1, max_value=50)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
@@ -78,6 +95,8 @@ class GrowthSimulationResultSerializer(serializers.Serializer):
|
|||||||
class CurrentFarmChartRequestSerializer(serializers.Serializer):
|
class CurrentFarmChartRequestSerializer(serializers.Serializer):
|
||||||
farm_uuid = serializers.UUIDField(help_text="شناسه یکتای مزرعه")
|
farm_uuid = serializers.UUIDField(help_text="شناسه یکتای مزرعه")
|
||||||
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
|
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
|
||||||
|
irrigation_recommendation = serializers.JSONField(required=False)
|
||||||
|
fertilization_recommendation = serializers.JSONField(required=False)
|
||||||
|
|
||||||
|
|
||||||
class CurrentFarmChartResponseSerializer(serializers.Serializer):
|
class CurrentFarmChartResponseSerializer(serializers.Serializer):
|
||||||
@@ -98,6 +117,8 @@ class CurrentFarmChartResponseSerializer(serializers.Serializer):
|
|||||||
class HarvestPredictionRequestSerializer(serializers.Serializer):
|
class HarvestPredictionRequestSerializer(serializers.Serializer):
|
||||||
farm_uuid = serializers.UUIDField(help_text="شناسه یکتای مزرعه")
|
farm_uuid = serializers.UUIDField(help_text="شناسه یکتای مزرعه")
|
||||||
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
|
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
|
||||||
|
irrigation_recommendation = serializers.JSONField(required=False)
|
||||||
|
fertilization_recommendation = serializers.JSONField(required=False)
|
||||||
|
|
||||||
|
|
||||||
class HarvestPredictionResponseSerializer(serializers.Serializer):
|
class HarvestPredictionResponseSerializer(serializers.Serializer):
|
||||||
@@ -113,6 +134,8 @@ class HarvestPredictionResponseSerializer(serializers.Serializer):
|
|||||||
class YieldPredictionRequestSerializer(serializers.Serializer):
|
class YieldPredictionRequestSerializer(serializers.Serializer):
|
||||||
farm_uuid = serializers.UUIDField(help_text="شناسه یکتای مزرعه")
|
farm_uuid = serializers.UUIDField(help_text="شناسه یکتای مزرعه")
|
||||||
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
|
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
|
||||||
|
irrigation_recommendation = serializers.JSONField(required=False)
|
||||||
|
fertilization_recommendation = serializers.JSONField(required=False)
|
||||||
|
|
||||||
|
|
||||||
class YieldPredictionResponseSerializer(serializers.Serializer):
|
class YieldPredictionResponseSerializer(serializers.Serializer):
|
||||||
@@ -138,6 +161,16 @@ class YieldHarvestSummaryQuerySerializer(serializers.Serializer):
|
|||||||
default=False,
|
default=False,
|
||||||
help_text="در صورت true بودن، بخش روایت نیز در آینده اضافه می شود.",
|
help_text="در صورت true بودن، بخش روایت نیز در آینده اضافه می شود.",
|
||||||
)
|
)
|
||||||
|
irrigation_recommendation = QueryJSONField(
|
||||||
|
required=False,
|
||||||
|
allow_null=True,
|
||||||
|
help_text="برنامه آبیاری به صورت JSON برای تزریق به PCSE.",
|
||||||
|
)
|
||||||
|
fertilization_recommendation = QueryJSONField(
|
||||||
|
required=False,
|
||||||
|
allow_null=True,
|
||||||
|
help_text="برنامه کودهی به صورت JSON برای تزریق به PCSE.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class YieldHarvestSummaryResponseSerializer(serializers.Serializer):
|
class YieldHarvestSummaryResponseSerializer(serializers.Serializer):
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ class PlantGrowthSimulationApiTests(TestCase):
|
|||||||
"weather": self.weather,
|
"weather": self.weather,
|
||||||
"soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.0},
|
"soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.0},
|
||||||
"site_parameters": {"WAV": 40.0},
|
"site_parameters": {"WAV": 40.0},
|
||||||
|
"irrigation_recommendation": {"events": [{"date": "2026-04-02", "amount": 2.5}]},
|
||||||
|
"fertilization_recommendation": {
|
||||||
|
"events": [{"date": "2026-04-02", "N_amount": 45, "N_recovery": 0.7}]
|
||||||
|
},
|
||||||
"page_size": 2,
|
"page_size": 2,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -95,12 +99,17 @@ class PlantGrowthSimulationApiTests(TestCase):
|
|||||||
"plant_name": self.plant.name,
|
"plant_name": self.plant.name,
|
||||||
"dynamic_parameters": ["DVS", "LAI"],
|
"dynamic_parameters": ["DVS", "LAI"],
|
||||||
"weather": self.weather,
|
"weather": self.weather,
|
||||||
|
"irrigation_recommendation": {"events": [{"date": "2026-04-02", "amount": 2.5}]},
|
||||||
|
"fertilization_recommendation": {
|
||||||
|
"events": [{"date": "2026-04-02", "N_amount": 45, "N_recovery": 0.7}]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 202)
|
self.assertEqual(response.status_code, 202)
|
||||||
self.assertEqual(response.json()["data"]["task_id"], "growth-task-1")
|
self.assertEqual(response.json()["data"]["task_id"], "growth-task-1")
|
||||||
|
self.assertEqual(mock_delay.call_args.args[0]["irrigation_recommendation"]["events"][0]["amount"], 2.5)
|
||||||
|
|
||||||
def test_queue_api_returns_400_for_missing_weather_and_farm_uuid(self):
|
def test_queue_api_returns_400_for_missing_weather_and_farm_uuid(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@@ -438,6 +447,8 @@ class PlantGrowthSimulationApiTests(TestCase):
|
|||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
"/yield-harvest-summary/?farm_uuid=550e8400-e29b-41d4-a716-446655440000"
|
"/yield-harvest-summary/?farm_uuid=550e8400-e29b-41d4-a716-446655440000"
|
||||||
"&season_year=1404&crop_name=wheat&include_narrative=true"
|
"&season_year=1404&crop_name=wheat&include_narrative=true"
|
||||||
|
"&irrigation_recommendation=%7B%22events%22%3A%5B%7B%22date%22%3A%222026-04-25%22%2C%22amount%22%3A2.5%7D%5D%7D"
|
||||||
|
"&fertilization_recommendation=%7B%22events%22%3A%5B%7B%22date%22%3A%222026-04-20%22%2C%22N_amount%22%3A45%2C%22N_recovery%22%3A0.7%7D%5D%7D"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
@@ -449,6 +460,23 @@ class PlantGrowthSimulationApiTests(TestCase):
|
|||||||
season_year="1404",
|
season_year="1404",
|
||||||
crop_name="wheat",
|
crop_name="wheat",
|
||||||
include_narrative=True,
|
include_narrative=True,
|
||||||
|
irrigation_recommendation={
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-25",
|
||||||
|
"amount": 2.5,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
fertilization_recommendation={
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-20",
|
||||||
|
"N_amount": 45,
|
||||||
|
"N_recovery": 0.7,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_yield_harvest_summary_api_returns_400_for_missing_farm_uuid(self):
|
def test_yield_harvest_summary_api_returns_400_for_missing_farm_uuid(self):
|
||||||
@@ -456,3 +484,12 @@ class PlantGrowthSimulationApiTests(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
self.assertEqual(response.json()["code"], 400)
|
self.assertEqual(response.json()["code"], 400)
|
||||||
|
|
||||||
|
def test_yield_harvest_summary_api_returns_400_for_invalid_json_recommendations(self):
|
||||||
|
response = self.client.get(
|
||||||
|
"/yield-harvest-summary/?farm_uuid=550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
"&irrigation_recommendation=%7Binvalid-json%7D"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
self.assertEqual(response.json()["code"], 400)
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ class PlantGrowthSimulationView(APIView):
|
|||||||
],
|
],
|
||||||
"soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.0},
|
"soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.0},
|
||||||
"site_parameters": {"WAV": 40.0},
|
"site_parameters": {"WAV": 40.0},
|
||||||
|
"irrigation_recommendation": {"events": [{"date": "2026-04-02", "amount": 2.5}]},
|
||||||
|
"fertilization_recommendation": {
|
||||||
|
"events": [{"date": "2026-04-02", "N_amount": 45, "N_recovery": 0.7}]
|
||||||
|
},
|
||||||
"page_size": 2,
|
"page_size": 2,
|
||||||
},
|
},
|
||||||
request_only=True,
|
request_only=True,
|
||||||
@@ -120,6 +124,10 @@ class PlantGrowthSimulationView(APIView):
|
|||||||
"plant_name": "گوجهفرنگی",
|
"plant_name": "گوجهفرنگی",
|
||||||
"dynamic_parameters": ["DVS", "LAI", "TAGP"],
|
"dynamic_parameters": ["DVS", "LAI", "TAGP"],
|
||||||
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
"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,
|
request_only=True,
|
||||||
),
|
),
|
||||||
@@ -246,6 +254,10 @@ class CurrentFarmSimulationChartView(APIView):
|
|||||||
value={
|
value={
|
||||||
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
"plant_name": "گوجهفرنگی",
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"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,
|
request_only=True,
|
||||||
),
|
),
|
||||||
@@ -303,6 +315,10 @@ class HarvestPredictionView(APIView):
|
|||||||
value={
|
value={
|
||||||
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
"plant_name": "گوجهفرنگی",
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"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,
|
request_only=True,
|
||||||
),
|
),
|
||||||
@@ -348,6 +364,10 @@ class YieldPredictionView(APIView):
|
|||||||
value={
|
value={
|
||||||
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
"plant_name": "گوجهفرنگی",
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"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,
|
request_only=True,
|
||||||
),
|
),
|
||||||
@@ -408,6 +428,20 @@ class YieldHarvestSummaryView(APIView):
|
|||||||
required=False,
|
required=False,
|
||||||
description="در آینده روایت متنی را نیز اضافه می کند.",
|
description="در آینده روایت متنی را نیز اضافه می کند.",
|
||||||
),
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="irrigation_recommendation",
|
||||||
|
type=str,
|
||||||
|
location=OpenApiParameter.QUERY,
|
||||||
|
required=False,
|
||||||
|
description="JSON برنامه آبیاری برای تزریق به شبیه سازی PCSE.",
|
||||||
|
),
|
||||||
|
OpenApiParameter(
|
||||||
|
name="fertilization_recommendation",
|
||||||
|
type=str,
|
||||||
|
location=OpenApiParameter.QUERY,
|
||||||
|
required=False,
|
||||||
|
description="JSON برنامه کودهی برای تزریق به شبیه سازی PCSE.",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
responses={
|
responses={
|
||||||
200: build_response(
|
200: build_response(
|
||||||
@@ -455,5 +489,7 @@ class YieldHarvestSummaryView(APIView):
|
|||||||
season_year=str(validated.get("season_year") or ""),
|
season_year=str(validated.get("season_year") or ""),
|
||||||
crop_name=validated.get("crop_name") or "",
|
crop_name=validated.get("crop_name") or "",
|
||||||
include_narrative=validated.get("include_narrative", False),
|
include_narrative=validated.get("include_narrative", False),
|
||||||
|
irrigation_recommendation=validated.get("irrigation_recommendation"),
|
||||||
|
fertilization_recommendation=validated.get("fertilization_recommendation"),
|
||||||
)
|
)
|
||||||
return Response({"code": 200, "msg": "موفق", "data": payload}, status=status.HTTP_200_OK)
|
return Response({"code": 200, "msg": "موفق", "data": payload}, status=status.HTTP_200_OK)
|
||||||
|
|||||||
@@ -103,7 +103,14 @@ class WaterStressSimulationService:
|
|||||||
raise GrowthSimulationError("Plant not found for the selected farm.")
|
raise GrowthSimulationError("Plant not found for the selected farm.")
|
||||||
return plant.name
|
return plant.name
|
||||||
|
|
||||||
def get_water_stress(self, *, farm_uuid: str, plant_name: str | None = None) -> dict[str, Any]:
|
def get_water_stress(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
farm_uuid: str,
|
||||||
|
plant_name: str | None = None,
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
resolved_plant_name = self._resolve_plant_name(farm_uuid=farm_uuid, plant_name=plant_name)
|
resolved_plant_name = self._resolve_plant_name(farm_uuid=farm_uuid, plant_name=plant_name)
|
||||||
context = build_growth_context(
|
context = build_growth_context(
|
||||||
{
|
{
|
||||||
@@ -111,7 +118,11 @@ class WaterStressSimulationService:
|
|||||||
"plant_name": resolved_plant_name,
|
"plant_name": resolved_plant_name,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
simulation_result, _scenario_id, simulation_warning = _run_simulation(context)
|
simulation_result, _scenario_id, simulation_warning = _run_simulation(
|
||||||
|
context,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
|
)
|
||||||
daily_output = simulation_result.get("daily_output") or []
|
daily_output = simulation_result.get("daily_output") or []
|
||||||
if not daily_output:
|
if not daily_output:
|
||||||
raise GrowthSimulationError("Water stress simulation produced no daily output.")
|
raise GrowthSimulationError("Water stress simulation produced no daily output.")
|
||||||
|
|||||||
@@ -32,16 +32,22 @@ class YieldHarvestSummaryService:
|
|||||||
season_year: str,
|
season_year: str,
|
||||||
crop_name: str,
|
crop_name: str,
|
||||||
include_narrative: bool = True,
|
include_narrative: bool = True,
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
farm_context = self._get_farm_context(farm_uuid)
|
farm_context = self._get_farm_context(farm_uuid)
|
||||||
farm_context["season_year"] = season_year
|
farm_context["season_year"] = season_year
|
||||||
farm_context["crop_name"] = crop_name or farm_context.get("crop_name") or ""
|
farm_context["crop_name"] = crop_name or farm_context.get("crop_name") or ""
|
||||||
|
farm_context["irrigation_recommendation"] = irrigation_recommendation or {}
|
||||||
|
farm_context["fertilization_recommendation"] = fertilization_recommendation or {}
|
||||||
yield_prediction = self._build_yield_prediction(
|
yield_prediction = self._build_yield_prediction(
|
||||||
farm_uuid=farm_uuid,
|
farm_uuid=farm_uuid,
|
||||||
season_year=season_year,
|
season_year=season_year,
|
||||||
crop_name=crop_name,
|
crop_name=crop_name,
|
||||||
include_narrative=include_narrative,
|
include_narrative=include_narrative,
|
||||||
farm_context=farm_context,
|
farm_context=farm_context,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
)
|
)
|
||||||
harvest_prediction_card = self._build_harvest_prediction_card(
|
harvest_prediction_card = self._build_harvest_prediction_card(
|
||||||
farm_uuid=farm_uuid,
|
farm_uuid=farm_uuid,
|
||||||
@@ -49,6 +55,8 @@ class YieldHarvestSummaryService:
|
|||||||
crop_name=crop_name,
|
crop_name=crop_name,
|
||||||
include_narrative=include_narrative,
|
include_narrative=include_narrative,
|
||||||
farm_context=farm_context,
|
farm_context=farm_context,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
)
|
)
|
||||||
harvest_readiness_zones = self._build_harvest_readiness_zones(
|
harvest_readiness_zones = self._build_harvest_readiness_zones(
|
||||||
farm_uuid=farm_uuid,
|
farm_uuid=farm_uuid,
|
||||||
@@ -75,6 +83,8 @@ class YieldHarvestSummaryService:
|
|||||||
crop_name=crop_name,
|
crop_name=crop_name,
|
||||||
include_narrative=include_narrative,
|
include_narrative=include_narrative,
|
||||||
farm_context=farm_context,
|
farm_context=farm_context,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
)
|
)
|
||||||
season_highlights_card = self._build_season_highlights_card(
|
season_highlights_card = self._build_season_highlights_card(
|
||||||
farm_uuid=farm_uuid,
|
farm_uuid=farm_uuid,
|
||||||
@@ -126,11 +136,15 @@ class YieldHarvestSummaryService:
|
|||||||
crop_name: str,
|
crop_name: str,
|
||||||
include_narrative: bool,
|
include_narrative: bool,
|
||||||
farm_context: dict[str, Any],
|
farm_context: dict[str, Any],
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
service = apps.get_app_config("crop_simulation").get_yield_prediction_service()
|
service = apps.get_app_config("crop_simulation").get_yield_prediction_service()
|
||||||
result = service.get_yield_prediction(
|
result = service.get_yield_prediction(
|
||||||
farm_uuid=farm_uuid,
|
farm_uuid=farm_uuid,
|
||||||
plant_name=crop_name or None,
|
plant_name=crop_name or None,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
)
|
)
|
||||||
supporting_metrics = dict(result.get("supportingMetrics") or {})
|
supporting_metrics = dict(result.get("supportingMetrics") or {})
|
||||||
|
|
||||||
@@ -173,11 +187,15 @@ class YieldHarvestSummaryService:
|
|||||||
crop_name: str,
|
crop_name: str,
|
||||||
include_narrative: bool,
|
include_narrative: bool,
|
||||||
farm_context: dict[str, Any],
|
farm_context: dict[str, Any],
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
service = apps.get_app_config("crop_simulation").get_harvest_prediction_service()
|
service = apps.get_app_config("crop_simulation").get_harvest_prediction_service()
|
||||||
result = service.get_harvest_prediction(
|
result = service.get_harvest_prediction(
|
||||||
farm_uuid=farm_uuid,
|
farm_uuid=farm_uuid,
|
||||||
plant_name=crop_name or None,
|
plant_name=crop_name or None,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
)
|
)
|
||||||
|
|
||||||
fallback_description = (
|
fallback_description = (
|
||||||
@@ -211,11 +229,15 @@ class YieldHarvestSummaryService:
|
|||||||
crop_name: str,
|
crop_name: str,
|
||||||
include_narrative: bool,
|
include_narrative: bool,
|
||||||
farm_context: dict[str, Any],
|
farm_context: dict[str, Any],
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
simulator = apps.get_app_config("crop_simulation").get_current_farm_chart_simulator()
|
simulator = apps.get_app_config("crop_simulation").get_current_farm_chart_simulator()
|
||||||
result = simulator.simulate(
|
result = simulator.simulate(
|
||||||
farm_uuid=farm_uuid,
|
farm_uuid=farm_uuid,
|
||||||
plant_name=crop_name or None,
|
plant_name=crop_name or None,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
)
|
)
|
||||||
pcse_timeseries = list(result.get("daily_output") or [])
|
pcse_timeseries = list(result.get("daily_output") or [])
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,20 @@ from .growth_simulation import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_yield_prediction_payload(*, farm_uuid: str, plant_name: str | None = None) -> dict[str, Any]:
|
def build_yield_prediction_payload(
|
||||||
|
*,
|
||||||
|
farm_uuid: str,
|
||||||
|
plant_name: str | None = None,
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
simulator = CurrentFarmChartSimulator()
|
simulator = CurrentFarmChartSimulator()
|
||||||
result = simulator.simulate(farm_uuid=farm_uuid, plant_name=plant_name)
|
result = simulator.simulate(
|
||||||
|
farm_uuid=farm_uuid,
|
||||||
|
plant_name=plant_name,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
|
)
|
||||||
yield_estimate = float((result.get("metrics") or {}).get("yield_estimate") or 0.0)
|
yield_estimate = float((result.get("metrics") or {}).get("yield_estimate") or 0.0)
|
||||||
predicted_yield_tons = round(max(yield_estimate / 1000.0, 0.0), 2)
|
predicted_yield_tons = round(max(yield_estimate / 1000.0, 0.0), 2)
|
||||||
return {
|
return {
|
||||||
@@ -31,8 +42,20 @@ def build_yield_prediction_payload(*, farm_uuid: str, plant_name: str | None = N
|
|||||||
|
|
||||||
|
|
||||||
class YieldPredictionService:
|
class YieldPredictionService:
|
||||||
def get_yield_prediction(self, *, farm_uuid: str, plant_name: str | None = None) -> dict[str, Any]:
|
def get_yield_prediction(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
farm_uuid: str,
|
||||||
|
plant_name: str | None = None,
|
||||||
|
irrigation_recommendation: dict[str, Any] | None = None,
|
||||||
|
fertilization_recommendation: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
return build_yield_prediction_payload(farm_uuid=farm_uuid, plant_name=plant_name)
|
return build_yield_prediction_payload(
|
||||||
|
farm_uuid=farm_uuid,
|
||||||
|
plant_name=plant_name,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
|
)
|
||||||
except GrowthSimulationError:
|
except GrowthSimulationError:
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
# لیست APIهایی که با استفاده از PCSE تحلیل انجام میدهند
|
||||||
|
|
||||||
|
این فایل APIهایی را فهرست میکند که در این پروژه یا مستقیماً از `PCSE` استفاده میکنند، یا بخشی از تحلیلشان به خروجیهای شبیهسازی `PCSE` وابسته است.
|
||||||
|
همچنین مشخص میکند:
|
||||||
|
|
||||||
|
- از چه مدل `PCSE` استفاده میشود
|
||||||
|
- ورودیهای لازم `PCSE` در این پروژه از کجا تأمین میشوند
|
||||||
|
- برنامه آبیاری، برنامه کودهی و داده آبوهوا چطور به مدل تزریق میشوند
|
||||||
|
|
||||||
|
## مدل PCSE مورد استفاده در پروژه
|
||||||
|
|
||||||
|
مدل پیشفرضی که در سرویس شبیهسازی استفاده میشود این است:
|
||||||
|
|
||||||
|
- `Wofost81_NWLP_CWB_CNB`
|
||||||
|
|
||||||
|
توضیح کوتاه:
|
||||||
|
|
||||||
|
- `Wofost81`: نسخه 8.1 از خانواده مدلهای WOFOST
|
||||||
|
- `NWLP`: شبیهسازی با محدودیت نیتروژن
|
||||||
|
- `CWB`: water balance
|
||||||
|
- `CNB`: carbon/nitrogen balance
|
||||||
|
|
||||||
|
بنابراین APIهایی که واقعاً از موتور اصلی شبیهسازی استفاده میکنند، عملاً روی این مدل اجرا میشوند مگر اینکه بعداً در کد override شده باشد.
|
||||||
|
|
||||||
|
## PCSE در این پروژه چه ورودیهایی میخواهد؟
|
||||||
|
|
||||||
|
در این پروژه لایه شبیهسازی برای اجرای `PCSE` این ورودیها را میسازد:
|
||||||
|
|
||||||
|
### 1. `weather`
|
||||||
|
رکوردهای آبوهوا با فیلدهای زیر:
|
||||||
|
|
||||||
|
- `DAY`
|
||||||
|
- `LAT`
|
||||||
|
- `LON`
|
||||||
|
- `ELEV`
|
||||||
|
- `IRRAD`
|
||||||
|
- `TMIN`
|
||||||
|
- `TMAX`
|
||||||
|
- `VAP`
|
||||||
|
- `WIND`
|
||||||
|
- `RAIN`
|
||||||
|
- `E0`
|
||||||
|
- `ES0`
|
||||||
|
- `ET0`
|
||||||
|
|
||||||
|
### 2. `soil`
|
||||||
|
پارامترهای خاک، از جمله:
|
||||||
|
|
||||||
|
- `SMFCF`
|
||||||
|
- `SMW`
|
||||||
|
- `SM0`
|
||||||
|
- `RDMSOL`
|
||||||
|
- `CRAIRC`
|
||||||
|
- `SOPE`
|
||||||
|
- `KSUB`
|
||||||
|
|
||||||
|
و در این پروژه بعضی شاخصهای کمکی هم کنار آن نگهداری میشوند، مثل:
|
||||||
|
|
||||||
|
- `nitrogen`
|
||||||
|
- `phosphorus`
|
||||||
|
- `potassium`
|
||||||
|
- `soil_ph`
|
||||||
|
- `electrical_conductivity`
|
||||||
|
|
||||||
|
### 3. `site_parameters`
|
||||||
|
پارامترهای سایت/شرایط اولیه، از جمله:
|
||||||
|
|
||||||
|
- `WAV`
|
||||||
|
- `SMLIM`
|
||||||
|
- `IFUNRN`
|
||||||
|
- `NOTINF`
|
||||||
|
- `SSI`
|
||||||
|
- `SSMAX`
|
||||||
|
- `NAVAILI`
|
||||||
|
|
||||||
|
### 4. `crop_parameters`
|
||||||
|
پارامترهای محصول. اینها یا از پروفایل شبیهسازی گیاه میآیند، یا اگر موجود نباشند بهصورت default ساخته میشوند. مهمترین defaultها:
|
||||||
|
|
||||||
|
- `crop_name`
|
||||||
|
- `TSUM1`
|
||||||
|
- `TSUM2`
|
||||||
|
- `YIELD_SCALE`
|
||||||
|
- `MAX_LAI`
|
||||||
|
- `MAX_BIOMASS`
|
||||||
|
|
||||||
|
### 5. `agromanagement`
|
||||||
|
تقویم و رویدادهای زراعی، شامل:
|
||||||
|
|
||||||
|
- `CropCalendar`
|
||||||
|
- `TimedEvents`
|
||||||
|
- `StateEvents`
|
||||||
|
|
||||||
|
همین بخش جایی است که برنامه آبیاری و برنامه کودهی به شبیهسازی تزریق میشود.
|
||||||
|
|
||||||
|
## ورودیها از کجا میآیند؟
|
||||||
|
|
||||||
|
### آبوهوا
|
||||||
|
منبع اصلی:
|
||||||
|
|
||||||
|
- جدول `WeatherForecast`
|
||||||
|
|
||||||
|
مسیر تولید:
|
||||||
|
|
||||||
|
- با `farm_uuid` مزرعه پیدا میشود
|
||||||
|
- از `center_location` مزرعه، forecastها خوانده میشوند
|
||||||
|
- معمولاً تا `14` روز آینده برداشته میشوند
|
||||||
|
- دادههای `precipitation` و `et0` که در دیتابیس به `mm/day` هستند، برای `PCSE` به `cm/day` تبدیل میشوند
|
||||||
|
|
||||||
|
فیلدهایی که از forecast استفاده میشوند:
|
||||||
|
|
||||||
|
- `forecast_date` → `DAY`
|
||||||
|
- `temperature_min` → `TMIN`
|
||||||
|
- `temperature_max` یا `temperature_mean` → `TMAX`
|
||||||
|
- `humidity_mean` → `VAP`
|
||||||
|
- `wind_speed_max` → `WIND`
|
||||||
|
- `precipitation` → `RAIN`
|
||||||
|
- `et0` → `E0`, `ES0`, `ET0`
|
||||||
|
|
||||||
|
اگر کاربر خودش `weather` را مستقیم بدهد، همان ورودی مستقیم استفاده میشود.
|
||||||
|
|
||||||
|
### خاک و وضعیت سایت
|
||||||
|
منبع اصلی:
|
||||||
|
|
||||||
|
- جدول `SensorData`
|
||||||
|
- رابطه `center_location.depths`
|
||||||
|
- بخشی از `sensor_payload`
|
||||||
|
|
||||||
|
نحوه ساخت:
|
||||||
|
|
||||||
|
- از لایه سطحی خاک (`top_depth`) پارامترهایی مثل `wv0033`, `wv1500`, `porosity` خوانده میشوند
|
||||||
|
- از روی آنها `SMFCF`, `SMW`, `SM0` ساخته میشود
|
||||||
|
- از `sensor_payload`، شاخصهایی مثل `soil_moisture`, `nitrogen`, `phosphorus`, `potassium`, `soil_ph`, `electrical_conductivity` استخراج میشود
|
||||||
|
- سپس از اینها `soil` و `site_parameters` نهایی ساخته میشود
|
||||||
|
|
||||||
|
### پارامترهای محصول
|
||||||
|
منبع اصلی:
|
||||||
|
|
||||||
|
- مدل `Plant`
|
||||||
|
|
||||||
|
اولویت تأمین:
|
||||||
|
|
||||||
|
1. `simulation profile` داخل یکی از این profileها:
|
||||||
|
- `growth_profile.simulation`
|
||||||
|
- `irrigation_profile.simulation`
|
||||||
|
- `health_profile.simulation`
|
||||||
|
2. اگر profile آماده وجود نداشته باشد، پارامترهای پیشفرض از روی اطلاعات رشد گیاه ساخته میشوند
|
||||||
|
|
||||||
|
### تقویم زراعی `agromanagement`
|
||||||
|
منبع اصلی:
|
||||||
|
|
||||||
|
- اگر در `simulation profile` گیاه موجود باشد، از همان استفاده میشود
|
||||||
|
- وگرنه بهصورت پیشفرض از بازه زمانی آبوهوای موجود ساخته میشود
|
||||||
|
|
||||||
|
ساختار پیشفرض:
|
||||||
|
|
||||||
|
- `crop_start_date` از اولین روز forecast
|
||||||
|
- `crop_end_date` از آخرین روز forecast یا کمی بعد از آن
|
||||||
|
- `TimedEvents` و `StateEvents` بهصورت اولیه خالی هستند
|
||||||
|
|
||||||
|
## 1) APIهای مستقیم و قطعی مبتنی بر PCSE
|
||||||
|
|
||||||
|
### 1. `POST /api/crop-simulation/growth/`
|
||||||
|
- کاربرد: اجرای شبیهسازی رشد گیاه.
|
||||||
|
- نقش PCSE: هسته اصلی این API اجرای مدل شبیهسازی `PCSE/WOFOST` است.
|
||||||
|
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- ورودیها:
|
||||||
|
- آبوهوا: از `WeatherForecast` یا ورودی مستقیم `weather`
|
||||||
|
- خاک: از `SensorData` و `center_location.depths`
|
||||||
|
- crop parameters: از `Plant` و `simulation profile` یا default
|
||||||
|
- agromanagement: از `simulation profile` یا default
|
||||||
|
- نوع استفاده: مستقیم.
|
||||||
|
|
||||||
|
### 2. `GET /api/crop-simulation/growth/<task_id>/status/`
|
||||||
|
- کاربرد: دریافت وضعیت و نتیجه شبیهسازی رشد.
|
||||||
|
- نقش PCSE: نتیجهای که برمیگرداند خروجی همان شبیهسازی مبتنی بر `PCSE` است.
|
||||||
|
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- نوع استفاده: مستقیم.
|
||||||
|
|
||||||
|
### 3. `POST /api/crop-simulation/current-farm-chart/`
|
||||||
|
- کاربرد: تولید chart وضعیت فعلی مزرعه.
|
||||||
|
- نقش PCSE: دادههای chart مثل `LAI`، `TAGP`، `TWSO`، `SM` و `daily_output` از شبیهسازی `PCSE` ساخته میشوند.
|
||||||
|
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- ورودیها:
|
||||||
|
- `farm_uuid`
|
||||||
|
- آبوهوا از `WeatherForecast`
|
||||||
|
- خاک/سایت از `SensorData` و دادههای خاک location
|
||||||
|
- پارامتر گیاه از `Plant`
|
||||||
|
- نوع استفاده: مستقیم.
|
||||||
|
|
||||||
|
### 4. `POST /api/crop-simulation/yield-prediction/`
|
||||||
|
- کاربرد: پیشبینی عملکرد مزرعه.
|
||||||
|
- نقش PCSE: عملکرد پیشبینیشده از خروجی شبیهسازی رشد/خروجیهای `PCSE` استخراج میشود.
|
||||||
|
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- ورودیها: همان ورودیهای `current-farm-chart`
|
||||||
|
- نوع استفاده: مستقیم.
|
||||||
|
|
||||||
|
### 5. `POST /api/crop-simulation/harvest-prediction/`
|
||||||
|
- کاربرد: پیشبینی زمان تقریبی برداشت.
|
||||||
|
- نقش PCSE: با استفاده از `daily_output`، `DVS` و سایر خروجیهای شبیهسازی، زمان رسیدن به برداشت برآورد میشود.
|
||||||
|
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- ورودیها: همان ورودیهای شبیهسازی رشد مزرعه
|
||||||
|
- نوع استفاده: مستقیم.
|
||||||
|
|
||||||
|
### 6. `GET /api/crop-simulation/yield-harvest-summary/`
|
||||||
|
- کاربرد: خلاصه عملکرد و برداشت.
|
||||||
|
- نقش PCSE: چند بخش این API مثل `yield_prediction`، `harvest_prediction_card` و `yield_prediction_chart` از خروجیهای شبیهسازی `PCSE` تغذیه میشوند.
|
||||||
|
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- ورودیها:
|
||||||
|
- خروجی `yield_prediction`
|
||||||
|
- خروجی `harvest_prediction`
|
||||||
|
- خروجی `current-farm-chart`
|
||||||
|
- همگی در نهایت متکی به همان ورودیهای farm/weather/soil/plant هستند
|
||||||
|
- نوع استفاده: مستقیم/ترکیبی.
|
||||||
|
|
||||||
|
### 7. `POST /api/irrigation/water-stress/`
|
||||||
|
- کاربرد: محاسبه شاخص تنش آبی مزرعه.
|
||||||
|
- نقش PCSE: این API از شبیهسازی `crop_simulation` استفاده میکند و شاخص تنش آبی را با تکیه بر خروجیهایی مثل `SM`، `DVS`، `ET0` و `RAIN` میسازد.
|
||||||
|
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- ورودیها:
|
||||||
|
- آبوهوا از `WeatherForecast`
|
||||||
|
- خاک و رطوبت خاک از `SensorData`
|
||||||
|
- پارامتر گیاه از `Plant`
|
||||||
|
- نوع استفاده: مستقیم، ولی شاخص نهایی یک فرمول داخلی روی خروجی شبیهسازی است.
|
||||||
|
|
||||||
|
## 2) APIهایی که بخشی از تحلیلشان ممکن است با PCSE انجام شود
|
||||||
|
|
||||||
|
### 8. `POST /api/irrigation/recommend/`
|
||||||
|
- کاربرد: تولید توصیه آبیاری.
|
||||||
|
- نقش PCSE: در صورت موجود بودن `simulation profile`، داده مزرعه و forecast مناسب، optimizer ابتدا سناریوهای آبیاری را با `PCSE` ارزیابی میکند.
|
||||||
|
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- برنامه آبیاری از کجا میآید؟
|
||||||
|
- ابتدا در خود سیستم چند strategy ساخته میشود
|
||||||
|
- بر اساس `daily_water_needs` و تعداد eventها، برای هر سناریو `irrigation_events` ساخته میشود
|
||||||
|
- این eventها به شکل `TimedEvents` با سیگنال `irrigate` وارد `agromanagement` میشوند
|
||||||
|
- آبوهوا از کجا میآید؟
|
||||||
|
- از forecastهای جدول `WeatherForecast`
|
||||||
|
- سایر ورودیها از کجا میآیند؟
|
||||||
|
- خاک و رطوبت و مواد غذایی: از `SensorData`
|
||||||
|
- گیاه و simulation profile: از `Plant`
|
||||||
|
- نکته: اگر شرایط کافی نباشد، به مسیر heuristic برمیگردد.
|
||||||
|
- نوع استفاده: مشروط/جزئی.
|
||||||
|
|
||||||
|
### 9. `POST /api/fertilization/recommend/`
|
||||||
|
- کاربرد: تولید توصیه کودهی.
|
||||||
|
- نقش PCSE: در صورت موجود بودن `simulation profile` و forecast، سناریوهای کودهی با `PCSE` شبیهسازی و امتیازدهی میشوند.
|
||||||
|
- مدل PCSE: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- برنامه کودهی از کجا میآید؟
|
||||||
|
- optimizer چند سناریوی کودهی میسازد
|
||||||
|
- برای هر سناریو event کودهی به شکل `TimedEvents`
|
||||||
|
- با سیگنال `apply_n`
|
||||||
|
- و payload شامل `N_amount` و `N_recovery`
|
||||||
|
- وارد `agromanagement` میشود
|
||||||
|
- آبوهوا از کجا میآید؟
|
||||||
|
- از forecastهای جدول `WeatherForecast`
|
||||||
|
- سایر ورودیها از کجا میآیند؟
|
||||||
|
- خاک و وضعیت عناصر از `SensorData`
|
||||||
|
- پروفایل گیاه از `Plant`
|
||||||
|
- نکته: اگر `PCSE` یا داده کافی در دسترس نباشد، fallback heuristic استفاده میشود.
|
||||||
|
- نوع استفاده: مشروط/جزئی.
|
||||||
|
|
||||||
|
## 3) APIهایی که از PCSE استفاده نمیکنند
|
||||||
|
|
||||||
|
این endpointها در همین حوزه هستند اما خودشان تحلیل مبتنی بر `PCSE` انجام نمیدهند:
|
||||||
|
|
||||||
|
- `POST /api/irrigation/plan-from-text/`
|
||||||
|
- `POST /api/fertilization/plan-from-text/`
|
||||||
|
|
||||||
|
این دو بیشتر parser متن آزاد هستند و برنامه را از متن به JSON ساختاریافته تبدیل میکنند.
|
||||||
|
|
||||||
|
## جمعبندی کوتاه
|
||||||
|
|
||||||
|
اگر بخواهیم فقط APIهایی را نام ببریم که واقعاً تحلیل یا شبیهسازی وابسته به `PCSE` دارند، مهمترینها اینها هستند:
|
||||||
|
|
||||||
|
- `POST /api/crop-simulation/growth/`
|
||||||
|
- `GET /api/crop-simulation/growth/<task_id>/status/`
|
||||||
|
- `POST /api/crop-simulation/current-farm-chart/`
|
||||||
|
- `POST /api/crop-simulation/yield-prediction/`
|
||||||
|
- `POST /api/crop-simulation/harvest-prediction/`
|
||||||
|
- `GET /api/crop-simulation/yield-harvest-summary/`
|
||||||
|
- `POST /api/irrigation/water-stress/`
|
||||||
|
- `POST /api/irrigation/recommend/` (مشروط)
|
||||||
|
- `POST /api/fertilization/recommend/` (مشروط)
|
||||||
|
|
||||||
|
## جمعبندی فنی خیلی کوتاه
|
||||||
|
|
||||||
|
- مدل اصلی `PCSE` در این پروژه: `Wofost81_NWLP_CWB_CNB`
|
||||||
|
- آبوهوا عمدتاً از `WeatherForecast` میآید
|
||||||
|
- خاک و رطوبت و بخشی از وضعیت تغذیه از `SensorData` و دادههای خاک location میآید
|
||||||
|
- پارامترهای گیاه و setup شبیهسازی از `Plant` و `simulation profile` میآید
|
||||||
|
- برنامه آبیاری و کودهی در optimizer ساخته میشوند و از طریق `TimedEvents` داخل `agromanagement` به `PCSE` تزریق میشوند
|
||||||
@@ -0,0 +1,743 @@
|
|||||||
|
# مستند کامل APIهای آپدیتشده مرتبط با PCSE
|
||||||
|
|
||||||
|
این فایل APIهایی را توضیح میدهد که اخیراً آپدیت شدهاند تا بتوانند `برنامه آبیاری` و `برنامه کودهی` را از ورودی بگیرند و به شبیهسازی `PCSE` پاس بدهند.
|
||||||
|
|
||||||
|
APIهای این سند:
|
||||||
|
|
||||||
|
- `POST /api/crop-simulation/growth/`
|
||||||
|
- `GET /api/crop-simulation/growth/<task_id>/status/`
|
||||||
|
- `POST /api/crop-simulation/current-farm-chart/`
|
||||||
|
- `POST /api/crop-simulation/yield-prediction/`
|
||||||
|
- `POST /api/crop-simulation/harvest-prediction/`
|
||||||
|
- `GET /api/crop-simulation/yield-harvest-summary/`
|
||||||
|
- `POST /api/irrigation/water-stress/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فرمت مشترک `irrigation_recommendation`
|
||||||
|
|
||||||
|
این فیلد در APIهای این سند میتواند ارسال شود:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-25",
|
||||||
|
"amount": 2.5,
|
||||||
|
"efficiency": 0.8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح فیلدها
|
||||||
|
|
||||||
|
- `events`: آرایهای از رویدادهای آبیاری
|
||||||
|
- `date`: تاریخ اجرای آبیاری
|
||||||
|
- `amount`: مقدار آبیاری برای event
|
||||||
|
- `efficiency`: راندمان آبیاری، اختیاری
|
||||||
|
|
||||||
|
### رفتار در PCSE
|
||||||
|
|
||||||
|
این دادهها به `TimedEvents` با سیگنال `irrigate` تبدیل میشوند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## فرمت مشترک `fertilization_recommendation`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-20",
|
||||||
|
"N_amount": 45,
|
||||||
|
"N_recovery": 0.7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح فیلدها
|
||||||
|
|
||||||
|
- `events`: آرایهای از رویدادهای کودهی
|
||||||
|
- `date`: تاریخ اجرای کودهی
|
||||||
|
- `N_amount`: مقدار نیتروژن
|
||||||
|
- `N_recovery`: ضریب بازیافت/جذب نیتروژن
|
||||||
|
|
||||||
|
### رفتار در PCSE
|
||||||
|
|
||||||
|
این دادهها به `TimedEvents` با سیگنال `apply_n` تبدیل میشوند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1) `POST /api/crop-simulation/growth/`
|
||||||
|
|
||||||
|
### کاربرد
|
||||||
|
|
||||||
|
شروع شبیهسازی رشد گیاه بهصورت async و برگرداندن `task_id`.
|
||||||
|
|
||||||
|
### ورودی
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"dynamic_parameters": ["DVS", "LAI", "TAGP", "TWSO", "SM"],
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"weather": [
|
||||||
|
{
|
||||||
|
"DAY": "2026-04-01",
|
||||||
|
"LAT": 35.7,
|
||||||
|
"LON": 51.4,
|
||||||
|
"TMIN": 12,
|
||||||
|
"TMAX": 24,
|
||||||
|
"RAIN": 0.0,
|
||||||
|
"ET0": 0.32
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"soil_parameters": {
|
||||||
|
"SMFCF": 0.34,
|
||||||
|
"SMW": 0.14,
|
||||||
|
"RDMSOL": 120.0
|
||||||
|
},
|
||||||
|
"site_parameters": {
|
||||||
|
"WAV": 40.0
|
||||||
|
},
|
||||||
|
"crop_parameters": {},
|
||||||
|
"agromanagement": {},
|
||||||
|
"irrigation_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-02",
|
||||||
|
"amount": 2.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fertilization_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-02",
|
||||||
|
"N_amount": 45,
|
||||||
|
"N_recovery": 0.7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"page_size": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح پارامترهای ورودی
|
||||||
|
|
||||||
|
- `plant_name`: نام گیاه
|
||||||
|
- `dynamic_parameters`: لیست پارامترهای دینامیک موردنیاز در خروجی
|
||||||
|
- `farm_uuid`: شناسه مزرعه؛ اگر باشد، داده مزرعه و forecast از سیستم خوانده میشود
|
||||||
|
- `weather`: آبوهوا بهصورت مستقیم؛ اگر `farm_uuid` نباشد لازم است
|
||||||
|
- `soil_parameters`: override برای پارامترهای خاک
|
||||||
|
- `site_parameters`: override برای پارامترهای site
|
||||||
|
- `crop_parameters`: override برای پارامترهای crop
|
||||||
|
- `agromanagement`: override برای تقویم زراعی
|
||||||
|
- `irrigation_recommendation`: برنامه آبیاری برای تزریق به PCSE
|
||||||
|
- `fertilization_recommendation`: برنامه کودهی برای تزریق به PCSE
|
||||||
|
- `page_size`: اندازه صفحه مراحل رشد در endpoint وضعیت task
|
||||||
|
|
||||||
|
### اعتبارسنجی
|
||||||
|
|
||||||
|
- حداقل یکی از `farm_uuid` یا `weather` باید ارسال شود
|
||||||
|
- `dynamic_parameters` نباید خالی باشد
|
||||||
|
- `page_size` باید بین `1` تا `50` باشد
|
||||||
|
|
||||||
|
### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 202,
|
||||||
|
"msg": "تسک شبیه سازی رشد در صف قرار گرفت.",
|
||||||
|
"data": {
|
||||||
|
"task_id": "growth-task-1",
|
||||||
|
"status_url": "/api/crop-simulation/growth/growth-task-1/status/",
|
||||||
|
"plant_name": "گوجهفرنگی"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح فیلدهای پاسخ
|
||||||
|
|
||||||
|
- `code`: کد داخلی پاسخ
|
||||||
|
- `msg`: پیام پاسخ
|
||||||
|
- `data.task_id`: شناسه task
|
||||||
|
- `data.status_url`: آدرس پیگیری وضعیت task
|
||||||
|
- `data.plant_name`: نام گیاه
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2) `GET /api/crop-simulation/growth/<task_id>/status/`
|
||||||
|
|
||||||
|
### کاربرد
|
||||||
|
|
||||||
|
بررسی وضعیت اجرای شبیهسازی رشد و دریافت نتیجه نهایی.
|
||||||
|
|
||||||
|
### پارامترهای Query
|
||||||
|
|
||||||
|
- `page`: شماره صفحه
|
||||||
|
- `page_size`: اندازه صفحه
|
||||||
|
|
||||||
|
### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "موفق",
|
||||||
|
"data": {
|
||||||
|
"task_id": "growth-task-1",
|
||||||
|
"status": "SUCCESS",
|
||||||
|
"status_fa": "موفق",
|
||||||
|
"result": {
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"dynamic_parameters": ["DVS", "LAI", "TAGP"],
|
||||||
|
"engine": "موتور شبیه سازی PCSE",
|
||||||
|
"model_name": "مدل ووفوست",
|
||||||
|
"scenario_id": 12,
|
||||||
|
"simulation_warning": null,
|
||||||
|
"summary_metrics": {
|
||||||
|
"yield_estimate": 5400.0,
|
||||||
|
"biomass": 9800.0,
|
||||||
|
"max_lai": 4.2
|
||||||
|
},
|
||||||
|
"stage_timeline": [
|
||||||
|
{
|
||||||
|
"order": 1,
|
||||||
|
"stage_code": "vegetative",
|
||||||
|
"stage_name": "رشد رویشی",
|
||||||
|
"start_date": "2026-04-01",
|
||||||
|
"end_date": "2026-04-05",
|
||||||
|
"days_count": 5,
|
||||||
|
"metrics": {
|
||||||
|
"DVS": {
|
||||||
|
"start": 0.1,
|
||||||
|
"end": 0.8,
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 0.8,
|
||||||
|
"avg": 0.45
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stages_page": [],
|
||||||
|
"pagination": {
|
||||||
|
"page": 1,
|
||||||
|
"page_size": 10,
|
||||||
|
"total_items": 1,
|
||||||
|
"total_pages": 1,
|
||||||
|
"has_next": false,
|
||||||
|
"has_previous": false
|
||||||
|
},
|
||||||
|
"daily_records_count": 14,
|
||||||
|
"default_page_size": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح کامل فیلدهای `result`
|
||||||
|
|
||||||
|
- `plant_name`: نام گیاه
|
||||||
|
- `dynamic_parameters`: پارامترهای پویا
|
||||||
|
- `engine`: نام فارسی موتور اجرا
|
||||||
|
- `model_name`: نام فارسی مدل
|
||||||
|
- `scenario_id`: شناسه سناریوی ذخیرهشده
|
||||||
|
- `simulation_warning`: هشدار fallback یا خطای غیرکشنده
|
||||||
|
- `summary_metrics.yield_estimate`: برآورد عملکرد
|
||||||
|
- `summary_metrics.biomass`: بیوماس
|
||||||
|
- `summary_metrics.max_lai`: بیشینه شاخص سطح برگ
|
||||||
|
- `stage_timeline`: خلاصه مراحل رشد
|
||||||
|
- `stages_page`: همان `stage_timeline` بهصورت صفحهبندیشده
|
||||||
|
- `pagination`: متادیتای صفحهبندی
|
||||||
|
- `daily_records_count`: تعداد رکوردهای روزانه
|
||||||
|
- `default_page_size`: اندازه صفحه پیشفرض
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3) `POST /api/crop-simulation/current-farm-chart/`
|
||||||
|
|
||||||
|
### کاربرد
|
||||||
|
|
||||||
|
ساخت chart وضعیت فعلی مزرعه بر اساس شبیهسازی.
|
||||||
|
|
||||||
|
### ورودی
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"irrigation_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-25",
|
||||||
|
"amount": 2.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fertilization_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-20",
|
||||||
|
"N_amount": 45,
|
||||||
|
"N_recovery": 0.7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح ورودی
|
||||||
|
|
||||||
|
- `farm_uuid`: شناسه مزرعه
|
||||||
|
- `plant_name`: نام گیاه، اختیاری
|
||||||
|
- `irrigation_recommendation`: برنامه آبیاری
|
||||||
|
- `fertilization_recommendation`: برنامه کودهی
|
||||||
|
|
||||||
|
### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "موفق",
|
||||||
|
"data": {
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"engine": "موتور شبیه سازی PCSE",
|
||||||
|
"model_name": "مدل ووفوست",
|
||||||
|
"scenario_id": 15,
|
||||||
|
"simulation_warning": null,
|
||||||
|
"categories": ["2026-04-01", "2026-04-02"],
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "تعداد برگ تخمینی",
|
||||||
|
"key": "leaf_count_estimate",
|
||||||
|
"data": [2400, 3600]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": [
|
||||||
|
{
|
||||||
|
"title": "تعداد برگ تخمینی",
|
||||||
|
"subtitle": "وضعیت فعلی",
|
||||||
|
"amount": 3600,
|
||||||
|
"unit": "برگ",
|
||||||
|
"avatarColor": "success",
|
||||||
|
"avatarIcon": "tabler-leaf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"current_state": {
|
||||||
|
"date": "2026-04-02",
|
||||||
|
"leaf_count_estimate": 3600,
|
||||||
|
"leaf_area_index": 0.3,
|
||||||
|
"biomass_weight": 120.5,
|
||||||
|
"storage_organ_weight": 0.0,
|
||||||
|
"soil_moisture_percent": 41.2,
|
||||||
|
"development_stage": 0.15,
|
||||||
|
"gdd": 12.5
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"yield_estimate": 5400,
|
||||||
|
"biomass": 9800,
|
||||||
|
"max_lai": 4.2
|
||||||
|
},
|
||||||
|
"daily_output": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح کامل پاسخ
|
||||||
|
|
||||||
|
- `categories`: محور تاریخ chart
|
||||||
|
- `series`: سریهای نمودار
|
||||||
|
- `summary`: کارتهای summary
|
||||||
|
- `current_state`: وضعیت آخرین روز شبیهسازی
|
||||||
|
- `metrics`: متریکهای خلاصه شبیهسازی
|
||||||
|
- `daily_output`: خروجی روزانه کامل PCSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4) `POST /api/crop-simulation/yield-prediction/`
|
||||||
|
|
||||||
|
### کاربرد
|
||||||
|
|
||||||
|
تبدیل خروجی شبیهسازی به پیشبینی عملکرد.
|
||||||
|
|
||||||
|
### ورودی
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"irrigation_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-25",
|
||||||
|
"amount": 2.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fertilization_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-20",
|
||||||
|
"N_amount": 45,
|
||||||
|
"N_recovery": 0.7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "موفق",
|
||||||
|
"data": {
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"predictedYieldTons": 5.4,
|
||||||
|
"predictedYieldRaw": 5400.0,
|
||||||
|
"unit": "تن",
|
||||||
|
"sourceUnit": "کیلوگرم در هکتار",
|
||||||
|
"simulationEngine": "موتور شبیه سازی PCSE",
|
||||||
|
"simulationModel": "مدل ووفوست",
|
||||||
|
"scenarioId": 15,
|
||||||
|
"simulationWarning": null,
|
||||||
|
"supportingMetrics": {
|
||||||
|
"yield_estimate": 5400.0,
|
||||||
|
"biomass": 9800.0,
|
||||||
|
"max_lai": 4.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح فیلدها
|
||||||
|
|
||||||
|
- `predictedYieldTons`: عملکرد پیشبینیشده بر حسب تن
|
||||||
|
- `predictedYieldRaw`: عملکرد خام بر حسب کیلوگرم در هکتار
|
||||||
|
- `unit`: واحد نهایی
|
||||||
|
- `sourceUnit`: واحد منبع
|
||||||
|
- `simulationEngine`: نام موتور
|
||||||
|
- `simulationModel`: نام مدل
|
||||||
|
- `scenarioId`: شناسه سناریو
|
||||||
|
- `simulationWarning`: هشدار احتمالی
|
||||||
|
- `supportingMetrics`: متریکهای پشتیبان شبیهسازی
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5) `POST /api/crop-simulation/harvest-prediction/`
|
||||||
|
|
||||||
|
### کاربرد
|
||||||
|
|
||||||
|
پیشبینی زمان تقریبی برداشت.
|
||||||
|
|
||||||
|
### ورودی
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"irrigation_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-25",
|
||||||
|
"amount": 2.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fertilization_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-20",
|
||||||
|
"N_amount": 45,
|
||||||
|
"N_recovery": 0.7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "موفق",
|
||||||
|
"data": {
|
||||||
|
"date": "2026-05-14",
|
||||||
|
"dateFormatted": "14 May 2026",
|
||||||
|
"daysUntil": 23,
|
||||||
|
"description": "توضیح زمان برداشت",
|
||||||
|
"optimalWindowStart": "2026-05-11",
|
||||||
|
"optimalWindowEnd": "2026-05-17",
|
||||||
|
"gddDetails": {
|
||||||
|
"current_cumulative_gdd": 50,
|
||||||
|
"required_gdd_for_maturity": 1200,
|
||||||
|
"remaining_gdd": 1150,
|
||||||
|
"estimated_days_to_harvest": 23,
|
||||||
|
"predicted_harvest_date": "2026-05-14",
|
||||||
|
"predicted_harvest_window": {
|
||||||
|
"start": "2026-05-11",
|
||||||
|
"end": "2026-05-17"
|
||||||
|
},
|
||||||
|
"daily_gdd_forecast": [],
|
||||||
|
"simulation_engine": "pcse",
|
||||||
|
"simulation_model_name": "Wofost81_NWLP_CWB_CNB",
|
||||||
|
"simulation_warning": null,
|
||||||
|
"scenario_id": 18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح فیلدها
|
||||||
|
|
||||||
|
- `date`: تاریخ برداشت پیشبینیشده
|
||||||
|
- `dateFormatted`: تاریخ فرمتشده برای UI
|
||||||
|
- `daysUntil`: تعداد روز تا برداشت
|
||||||
|
- `description`: توضیح خلاصه
|
||||||
|
- `optimalWindowStart`: شروع بازه بهینه برداشت
|
||||||
|
- `optimalWindowEnd`: پایان بازه بهینه برداشت
|
||||||
|
- `gddDetails`: جزئیات مبتنی بر GDD و شبیهسازی
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6) `GET /api/crop-simulation/yield-harvest-summary/`
|
||||||
|
|
||||||
|
### کاربرد
|
||||||
|
|
||||||
|
ساخت داشبورد خلاصه عملکرد و برداشت.
|
||||||
|
|
||||||
|
### ورودی
|
||||||
|
|
||||||
|
این API از query string استفاده میکند.
|
||||||
|
|
||||||
|
### پارامترهای query
|
||||||
|
|
||||||
|
- `farm_uuid`: شناسه مزرعه
|
||||||
|
- `season_year`: سال زراعی
|
||||||
|
- `crop_name`: نام محصول
|
||||||
|
- `include_narrative`: اگر `true` باشد، تلاش برای تولید narrative میشود
|
||||||
|
- `irrigation_recommendation`: JSON string برنامه آبیاری
|
||||||
|
- `fertilization_recommendation`: JSON string برنامه کودهی
|
||||||
|
|
||||||
|
### نمونه درخواست
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/crop-simulation/yield-harvest-summary/?farm_uuid=11111111-1111-1111-1111-111111111111&season_year=1404&crop_name=wheat&include_narrative=true&irrigation_recommendation={"events":[{"date":"2026-04-25","amount":2.5}]}&fertilization_recommendation={"events":[{"date":"2026-04-20","N_amount":45,"N_recovery":0.7}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "موفق",
|
||||||
|
"data": {
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"season_highlights_card": {
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"crop_name": "wheat",
|
||||||
|
"season_year": "1404",
|
||||||
|
"title": "خلاصه فصل",
|
||||||
|
"subtitle": "",
|
||||||
|
"total_predicted_yield": 5.4,
|
||||||
|
"yield_unit": "تن",
|
||||||
|
"target_harvest_date": "2026-05-14",
|
||||||
|
"days_until_harvest": 23,
|
||||||
|
"average_readiness": 74,
|
||||||
|
"primary_quality_grade": "A",
|
||||||
|
"estimated_revenue": null,
|
||||||
|
"soil_type": "loam"
|
||||||
|
},
|
||||||
|
"yield_prediction": {},
|
||||||
|
"harvest_prediction_card": {},
|
||||||
|
"harvest_readiness_zones": {},
|
||||||
|
"yield_quality_bands": {},
|
||||||
|
"harvest_operations_card": {},
|
||||||
|
"yield_prediction_chart": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ساختار response
|
||||||
|
|
||||||
|
- `season_highlights_card`: خلاصه فصل
|
||||||
|
- `yield_prediction`: بلوک پیشبینی عملکرد
|
||||||
|
- `harvest_prediction_card`: بلوک پیشبینی برداشت
|
||||||
|
- `harvest_readiness_zones`: آمادگی برداشت نواحی
|
||||||
|
- `yield_quality_bands`: باندهای کیفیت محصول
|
||||||
|
- `harvest_operations_card`: پیشنهاد عملیات برداشت
|
||||||
|
- `yield_prediction_chart`: نمودار عملکرد و بیوماس
|
||||||
|
|
||||||
|
### توضیح مهم
|
||||||
|
|
||||||
|
این API بخشهایی از response را از چند سرویس مختلف میسازد، بنابراین بعضی بلوکها ساختار nested و بزرگ دارند. مخصوصاً:
|
||||||
|
|
||||||
|
- `yield_prediction`
|
||||||
|
- `harvest_prediction_card`
|
||||||
|
- `yield_prediction_chart`
|
||||||
|
|
||||||
|
این سه بخش مستقیماً تحت تأثیر `irrigation_recommendation` و `fertilization_recommendation` قرار میگیرند چون از اجرای PCSE استفاده میکنند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7) `POST /api/irrigation/water-stress/`
|
||||||
|
|
||||||
|
### کاربرد
|
||||||
|
|
||||||
|
محاسبه شاخص تنش آبی با استفاده از خروجی شبیهسازی.
|
||||||
|
|
||||||
|
### ورودی
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"irrigation_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-25",
|
||||||
|
"amount": 2.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fertilization_recommendation": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"date": "2026-04-20",
|
||||||
|
"N_amount": 45,
|
||||||
|
"N_recovery": 0.7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح ورودی
|
||||||
|
|
||||||
|
- `farm_uuid`: شناسه مزرعه
|
||||||
|
- `sensor_uuid`: نام قدیمی برای `farm_uuid`
|
||||||
|
- `plant_name`: نام گیاه، اختیاری
|
||||||
|
- `irrigation_recommendation`: برنامه آبیاری
|
||||||
|
- `fertilization_recommendation`: برنامه کودهی
|
||||||
|
|
||||||
|
### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": {
|
||||||
|
"farm_uuid": "11111111-1111-1111-1111-111111111111",
|
||||||
|
"plant_name": "گوجهفرنگی",
|
||||||
|
"waterStressIndex": 37,
|
||||||
|
"level": "متوسط",
|
||||||
|
"sourceMetric": {
|
||||||
|
"soilMoisturePercent": 46.0,
|
||||||
|
"availableWaterRatio": 0.72,
|
||||||
|
"fieldCapacity": 0.34,
|
||||||
|
"wiltingPoint": 0.14,
|
||||||
|
"rootDepthCm": 120.0,
|
||||||
|
"recentEt0": 0.33,
|
||||||
|
"recentRain": 1.0,
|
||||||
|
"soilMoistureDrop": 4.2,
|
||||||
|
"developmentStage": 1.02,
|
||||||
|
"stageCode": "flowering",
|
||||||
|
"stageSensitivity": 1.2,
|
||||||
|
"engine": "crop_simulation",
|
||||||
|
"formula": "stress = clamp(((moisture_deficit + et0_pressure + trend_pressure - rainfall_relief - root_depth_relief) * stage_sensitivity), 0, 100)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### توضیح فیلدهای response
|
||||||
|
|
||||||
|
- `farm_uuid`: شناسه مزرعه
|
||||||
|
- `plant_name`: نام گیاه resolved شده
|
||||||
|
- `waterStressIndex`: شاخص نهایی تنش آبی بین `0` تا `100`
|
||||||
|
- `level`: سطح تنش آبی (`پایین`، `متوسط`، `بالا`)
|
||||||
|
- `sourceMetric`: جزئیات محاسبه
|
||||||
|
|
||||||
|
### توضیح `sourceMetric`
|
||||||
|
|
||||||
|
- `soilMoisturePercent`: درصد رطوبت خاک
|
||||||
|
- `availableWaterRatio`: نسبت آب قابل استفاده
|
||||||
|
- `fieldCapacity`: ظرفیت مزرعه
|
||||||
|
- `wiltingPoint`: نقطه پژمردگی
|
||||||
|
- `rootDepthCm`: عمق ریشه
|
||||||
|
- `recentEt0`: تبخیر و تعرق مرجع اخیر
|
||||||
|
- `recentRain`: بارش اخیر
|
||||||
|
- `soilMoistureDrop`: افت رطوبت خاک
|
||||||
|
- `developmentStage`: مرحله رشد عددی
|
||||||
|
- `stageCode`: کد مرحله رشد
|
||||||
|
- `stageSensitivity`: حساسیت مرحله رشد
|
||||||
|
- `engine`: منبع محاسبه
|
||||||
|
- `formula`: فرمول محاسبه شاخص
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## خطاهای رایج
|
||||||
|
|
||||||
|
### خطای 400
|
||||||
|
|
||||||
|
وقتی ورودی نامعتبر باشد:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "داده نامعتبر.",
|
||||||
|
"data": {
|
||||||
|
"farm_uuid": ["farm_uuid الزامی است."]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### خطای 404
|
||||||
|
|
||||||
|
بیشتر در `water-stress` وقتی مزرعه پیدا نشود:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 404,
|
||||||
|
"msg": "Farm not found.",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### خطای 500
|
||||||
|
|
||||||
|
وقتی اجرای شبیهسازی یا ساخت response شکست بخورد:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 500,
|
||||||
|
"msg": "خطا در پیش بینی عملکرد: ...",
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## جمعبندی
|
||||||
|
|
||||||
|
APIهایی که در این سند توضیح داده شدند، حالا میتوانند از ورودی کاربر:
|
||||||
|
|
||||||
|
- `irrigation_recommendation`
|
||||||
|
- `fertilization_recommendation`
|
||||||
|
|
||||||
|
را دریافت کنند و آنها را به لایه شبیهسازی `PCSE` پاس بدهند. این یعنی خروجیهای:
|
||||||
|
|
||||||
|
- رشد
|
||||||
|
- chart مزرعه
|
||||||
|
- پیشبینی عملکرد
|
||||||
|
- پیشبینی برداشت
|
||||||
|
- خلاصه Yield/Harvest
|
||||||
|
- شاخص تنش آبی
|
||||||
|
|
||||||
|
میتوانند تحت تأثیر برنامه آبیاری و برنامه کودهی ارسالی کاربر قرار بگیرند.
|
||||||
@@ -5,7 +5,14 @@ from django.apps import apps
|
|||||||
from farm_data.models import SensorData
|
from farm_data.models import SensorData
|
||||||
|
|
||||||
class WaterStressService:
|
class WaterStressService:
|
||||||
def get_water_stress(self, *, farm_uuid: str, plant_name: str | None = None) -> dict[str, Any]:
|
def get_water_stress(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
farm_uuid: str,
|
||||||
|
plant_name: str | None = None,
|
||||||
|
irrigation_recommendation: dict | None = None,
|
||||||
|
fertilization_recommendation: dict | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
sensor = SensorData.objects.filter(farm_uuid=farm_uuid).first()
|
sensor = SensorData.objects.filter(farm_uuid=farm_uuid).first()
|
||||||
if sensor is None:
|
if sensor is None:
|
||||||
raise ValueError("Farm not found.")
|
raise ValueError("Farm not found.")
|
||||||
@@ -14,6 +21,8 @@ class WaterStressService:
|
|||||||
return simulation_service.get_water_stress(
|
return simulation_service.get_water_stress(
|
||||||
farm_uuid=str(sensor.farm_uuid),
|
farm_uuid=str(sensor.farm_uuid),
|
||||||
plant_name=plant_name,
|
plant_name=plant_name,
|
||||||
|
irrigation_recommendation=irrigation_recommendation,
|
||||||
|
fertilization_recommendation=fertilization_recommendation,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ class IrrigationRecommendResponseSerializer(serializers.Serializer):
|
|||||||
class WaterStressRequestSerializer(serializers.Serializer):
|
class WaterStressRequestSerializer(serializers.Serializer):
|
||||||
farm_uuid = serializers.CharField(required=False, help_text="شناسه یکتای مزرعه")
|
farm_uuid = serializers.CharField(required=False, help_text="شناسه یکتای مزرعه")
|
||||||
sensor_uuid = serializers.CharField(required=False, help_text="نام قدیمی برای farm_uuid")
|
sensor_uuid = serializers.CharField(required=False, help_text="نام قدیمی برای farm_uuid")
|
||||||
|
plant_name = serializers.CharField(required=False, allow_blank=True, help_text="نام گیاه")
|
||||||
|
irrigation_recommendation = serializers.JSONField(required=False)
|
||||||
|
fertilization_recommendation = serializers.JSONField(required=False)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
farm_uuid = attrs.get("farm_uuid") or attrs.get("sensor_uuid")
|
farm_uuid = attrs.get("farm_uuid") or attrs.get("sensor_uuid")
|
||||||
|
|||||||
@@ -30,7 +30,13 @@ class WaterStressApiTests(TestCase):
|
|||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/water-stress/",
|
"/water-stress/",
|
||||||
data={"farm_uuid": "550e8400-e29b-41d4-a716-446655440000"},
|
data={
|
||||||
|
"farm_uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"irrigation_recommendation": {"events": [{"date": "2026-04-25", "amount": 2.5}]},
|
||||||
|
"fertilization_recommendation": {
|
||||||
|
"events": [{"date": "2026-04-20", "N_amount": 45, "N_recovery": 0.7}]
|
||||||
|
},
|
||||||
|
},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+13
-2
@@ -354,7 +354,13 @@ class WaterStressView(APIView):
|
|||||||
examples=[
|
examples=[
|
||||||
OpenApiExample(
|
OpenApiExample(
|
||||||
"نمونه درخواست water stress",
|
"نمونه درخواست water stress",
|
||||||
value={"farm_uuid": "11111111-1111-1111-1111-111111111111"},
|
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,
|
request_only=True,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -368,7 +374,12 @@ class WaterStressView(APIView):
|
|||||||
)
|
)
|
||||||
service = apps.get_app_config("irrigation").get_water_stress_service()
|
service = apps.get_app_config("irrigation").get_water_stress_service()
|
||||||
try:
|
try:
|
||||||
data = service.get_water_stress(farm_uuid=serializer.validated_data["farm_uuid"])
|
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:
|
except ValueError as exc:
|
||||||
return Response(
|
return Response(
|
||||||
{"code": 404, "msg": str(exc), "data": None},
|
{"code": 404, "msg": str(exc), "data": None},
|
||||||
|
|||||||
Reference in New Issue
Block a user