From ef593153babbac0db94a929a459445e03435340d Mon Sep 17 00:00:00 2001 From: Mohammad Sajad Pourajam Date: Sat, 2 May 2026 14:03:48 +0330 Subject: [PATCH] UPDATE --- crop_simulation/growth_simulation.py | 30 +- crop_simulation/harvest_prediction.py | 30 +- crop_simulation/serializers.py | 33 + crop_simulation/test_growth_simulation_api.py | 37 + crop_simulation/views.py | 36 + crop_simulation/water_stress.py | 15 +- crop_simulation/yield_harvest_summary.py | 22 + crop_simulation/yield_prediction.py | 31 +- docs/pcse_api_list.md | 290 +++++++ docs/updated_pcse_apis_reference.md | 743 ++++++++++++++++++ irrigation/indicators.py | 11 +- irrigation/serializers.py | 3 + irrigation/test_water_stress_api.py | 8 +- irrigation/views.py | 15 +- 14 files changed, 1286 insertions(+), 18 deletions(-) create mode 100644 docs/pcse_api_list.md create mode 100644 docs/updated_pcse_apis_reference.md diff --git a/crop_simulation/growth_simulation.py b/crop_simulation/growth_simulation.py index 100e5b8..0b179ea 100644 --- a/crop_simulation/growth_simulation.py +++ b/crop_simulation/growth_simulation.py @@ -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: response = CropSimulationService().run_single_simulation( farm_uuid=context.farm_uuid, @@ -480,6 +485,8 @@ def _run_simulation(context: GrowthSimulationContext) -> tuple[dict[str, Any], i crop_parameters=context.crop_parameters, agromanagement=context.agromanagement, site_parameters=context.site_parameters, + irrigation_recommendation=irrigation_recommendation, + fertilization_recommendation=fertilization_recommendation, name=f"growth:{context.plant_name}", ) 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"}, ) - 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: progress_callback( state="PROGRESS", @@ -751,7 +762,14 @@ def _build_current_farm_chart_payload( class CurrentFarmChartSimulator: """سازنده 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: raise GrowthSimulationError("ارسال farm_uuid الزامی است.") @@ -777,7 +795,11 @@ class CurrentFarmChartSimulator: "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( context, simulation_result, diff --git a/crop_simulation/harvest_prediction.py b/crop_simulation/harvest_prediction.py index 105327e..5ad1b8e 100644 --- a/crop_simulation/harvest_prediction.py +++ b/crop_simulation/harvest_prediction.py @@ -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 if not resolved_plant_name: 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, } ) - 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 [] if not daily_output: raise GrowthSimulationError("هیچ خروجی شبیه سازی در دسترس نیست.") @@ -146,5 +156,17 @@ def build_harvest_prediction_payload(*, farm_uuid: str, plant_name: str | None = class HarvestPredictionService: - def get_harvest_prediction(self, *, farm_uuid: str, plant_name: str | None = None) -> dict[str, Any]: - return build_harvest_prediction_payload(farm_uuid=farm_uuid, plant_name=plant_name) + def get_harvest_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]: + return build_harvest_prediction_payload( + farm_uuid=farm_uuid, + plant_name=plant_name, + irrigation_recommendation=irrigation_recommendation, + fertilization_recommendation=fertilization_recommendation, + ) diff --git a/crop_simulation/serializers.py b/crop_simulation/serializers.py index b51374a..e33e808 100644 --- a/crop_simulation/serializers.py +++ b/crop_simulation/serializers.py @@ -1,8 +1,23 @@ from __future__ import annotations +import json + 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): plant_name = serializers.CharField(help_text="نام گیاه") dynamic_parameters = serializers.ListField( @@ -16,6 +31,8 @@ class GrowthSimulationRequestSerializer(serializers.Serializer): site_parameters = serializers.JSONField(required=False) crop_parameters = 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) def validate(self, attrs): @@ -78,6 +95,8 @@ class GrowthSimulationResultSerializer(serializers.Serializer): class CurrentFarmChartRequestSerializer(serializers.Serializer): farm_uuid = serializers.UUIDField(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): @@ -98,6 +117,8 @@ class CurrentFarmChartResponseSerializer(serializers.Serializer): class HarvestPredictionRequestSerializer(serializers.Serializer): farm_uuid = serializers.UUIDField(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): @@ -113,6 +134,8 @@ class HarvestPredictionResponseSerializer(serializers.Serializer): class YieldPredictionRequestSerializer(serializers.Serializer): farm_uuid = serializers.UUIDField(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): @@ -138,6 +161,16 @@ class YieldHarvestSummaryQuerySerializer(serializers.Serializer): default=False, 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): diff --git a/crop_simulation/test_growth_simulation_api.py b/crop_simulation/test_growth_simulation_api.py index 6e7aff6..4eccbbb 100644 --- a/crop_simulation/test_growth_simulation_api.py +++ b/crop_simulation/test_growth_simulation_api.py @@ -76,6 +76,10 @@ class PlantGrowthSimulationApiTests(TestCase): "weather": self.weather, "soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.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, } ) @@ -95,12 +99,17 @@ class PlantGrowthSimulationApiTests(TestCase): "plant_name": self.plant.name, "dynamic_parameters": ["DVS", "LAI"], "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", ) self.assertEqual(response.status_code, 202) 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): response = self.client.post( @@ -438,6 +447,8 @@ class PlantGrowthSimulationApiTests(TestCase): response = self.client.get( "/yield-harvest-summary/?farm_uuid=550e8400-e29b-41d4-a716-446655440000" "&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) @@ -449,6 +460,23 @@ class PlantGrowthSimulationApiTests(TestCase): 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, + } + ] + }, ) 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.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) diff --git a/crop_simulation/views.py b/crop_simulation/views.py index f3931b9..970edac 100644 --- a/crop_simulation/views.py +++ b/crop_simulation/views.py @@ -110,6 +110,10 @@ class PlantGrowthSimulationView(APIView): ], "soil_parameters": {"SMFCF": 0.34, "SMW": 0.14, "RDMSOL": 120.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, }, request_only=True, @@ -120,6 +124,10 @@ class PlantGrowthSimulationView(APIView): "plant_name": "گوجه‌فرنگی", "dynamic_parameters": ["DVS", "LAI", "TAGP"], "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, ), @@ -246,6 +254,10 @@ class CurrentFarmSimulationChartView(APIView): value={ "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}] + }, }, request_only=True, ), @@ -303,6 +315,10 @@ class HarvestPredictionView(APIView): value={ "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}] + }, }, request_only=True, ), @@ -348,6 +364,10 @@ class YieldPredictionView(APIView): value={ "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}] + }, }, request_only=True, ), @@ -408,6 +428,20 @@ class YieldHarvestSummaryView(APIView): required=False, 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={ 200: build_response( @@ -455,5 +489,7 @@ class YieldHarvestSummaryView(APIView): season_year=str(validated.get("season_year") or ""), crop_name=validated.get("crop_name") or "", 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) diff --git a/crop_simulation/water_stress.py b/crop_simulation/water_stress.py index a5ba06d..0954ed6 100644 --- a/crop_simulation/water_stress.py +++ b/crop_simulation/water_stress.py @@ -103,7 +103,14 @@ class WaterStressSimulationService: raise GrowthSimulationError("Plant not found for the selected farm.") 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) context = build_growth_context( { @@ -111,7 +118,11 @@ class WaterStressSimulationService: "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 [] if not daily_output: raise GrowthSimulationError("Water stress simulation produced no daily output.") diff --git a/crop_simulation/yield_harvest_summary.py b/crop_simulation/yield_harvest_summary.py index 17ed669..02eb42d 100644 --- a/crop_simulation/yield_harvest_summary.py +++ b/crop_simulation/yield_harvest_summary.py @@ -32,16 +32,22 @@ class YieldHarvestSummaryService: season_year: str, crop_name: str, include_narrative: bool = True, + irrigation_recommendation: dict[str, Any] | None = None, + fertilization_recommendation: dict[str, Any] | None = None, ) -> dict[str, Any]: farm_context = self._get_farm_context(farm_uuid) farm_context["season_year"] = season_year 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( farm_uuid=farm_uuid, season_year=season_year, crop_name=crop_name, include_narrative=include_narrative, farm_context=farm_context, + irrigation_recommendation=irrigation_recommendation, + fertilization_recommendation=fertilization_recommendation, ) harvest_prediction_card = self._build_harvest_prediction_card( farm_uuid=farm_uuid, @@ -49,6 +55,8 @@ class YieldHarvestSummaryService: crop_name=crop_name, include_narrative=include_narrative, farm_context=farm_context, + irrigation_recommendation=irrigation_recommendation, + fertilization_recommendation=fertilization_recommendation, ) harvest_readiness_zones = self._build_harvest_readiness_zones( farm_uuid=farm_uuid, @@ -75,6 +83,8 @@ class YieldHarvestSummaryService: crop_name=crop_name, include_narrative=include_narrative, farm_context=farm_context, + irrigation_recommendation=irrigation_recommendation, + fertilization_recommendation=fertilization_recommendation, ) season_highlights_card = self._build_season_highlights_card( farm_uuid=farm_uuid, @@ -126,11 +136,15 @@ class YieldHarvestSummaryService: crop_name: str, include_narrative: bool, farm_context: dict[str, Any], + irrigation_recommendation: dict[str, Any] | None = None, + fertilization_recommendation: dict[str, Any] | None = None, ) -> dict[str, Any]: service = apps.get_app_config("crop_simulation").get_yield_prediction_service() result = service.get_yield_prediction( farm_uuid=farm_uuid, plant_name=crop_name or None, + irrigation_recommendation=irrigation_recommendation, + fertilization_recommendation=fertilization_recommendation, ) supporting_metrics = dict(result.get("supportingMetrics") or {}) @@ -173,11 +187,15 @@ class YieldHarvestSummaryService: crop_name: str, include_narrative: bool, farm_context: dict[str, Any], + irrigation_recommendation: dict[str, Any] | None = None, + fertilization_recommendation: dict[str, Any] | None = None, ) -> dict[str, Any]: service = apps.get_app_config("crop_simulation").get_harvest_prediction_service() result = service.get_harvest_prediction( farm_uuid=farm_uuid, plant_name=crop_name or None, + irrigation_recommendation=irrigation_recommendation, + fertilization_recommendation=fertilization_recommendation, ) fallback_description = ( @@ -211,11 +229,15 @@ class YieldHarvestSummaryService: crop_name: str, include_narrative: bool, farm_context: dict[str, Any], + irrigation_recommendation: dict[str, Any] | None = None, + fertilization_recommendation: dict[str, Any] | None = None, ) -> dict[str, Any]: simulator = apps.get_app_config("crop_simulation").get_current_farm_chart_simulator() result = simulator.simulate( farm_uuid=farm_uuid, plant_name=crop_name or None, + irrigation_recommendation=irrigation_recommendation, + fertilization_recommendation=fertilization_recommendation, ) pcse_timeseries = list(result.get("daily_output") or []) diff --git a/crop_simulation/yield_prediction.py b/crop_simulation/yield_prediction.py index 326d483..995133d 100644 --- a/crop_simulation/yield_prediction.py +++ b/crop_simulation/yield_prediction.py @@ -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() - 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) predicted_yield_tons = round(max(yield_estimate / 1000.0, 0.0), 2) return { @@ -31,8 +42,20 @@ def build_yield_prediction_payload(*, farm_uuid: str, plant_name: str | None = N 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: - 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: raise diff --git a/docs/pcse_api_list.md b/docs/pcse_api_list.md new file mode 100644 index 0000000..c5c02a3 --- /dev/null +++ b/docs/pcse_api_list.md @@ -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//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//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` تزریق می‌شوند diff --git a/docs/updated_pcse_apis_reference.md b/docs/updated_pcse_apis_reference.md new file mode 100644 index 0000000..e134dc9 --- /dev/null +++ b/docs/updated_pcse_apis_reference.md @@ -0,0 +1,743 @@ +# مستند کامل APIهای آپدیت‌شده مرتبط با PCSE + +این فایل APIهایی را توضیح می‌دهد که اخیراً آپدیت شده‌اند تا بتوانند `برنامه آبیاری` و `برنامه کودهی` را از ورودی بگیرند و به شبیه‌سازی `PCSE` پاس بدهند. + +APIهای این سند: + +- `POST /api/crop-simulation/growth/` +- `GET /api/crop-simulation/growth//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//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 +- شاخص تنش آبی + +می‌توانند تحت تأثیر برنامه آبیاری و برنامه کودهی ارسالی کاربر قرار بگیرند. diff --git a/irrigation/indicators.py b/irrigation/indicators.py index 9eb4d84..9ab9142 100644 --- a/irrigation/indicators.py +++ b/irrigation/indicators.py @@ -5,7 +5,14 @@ from django.apps import apps from farm_data.models import SensorData 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() if sensor is None: raise ValueError("Farm not found.") @@ -14,6 +21,8 @@ class WaterStressService: return simulation_service.get_water_stress( farm_uuid=str(sensor.farm_uuid), plant_name=plant_name, + irrigation_recommendation=irrigation_recommendation, + fertilization_recommendation=fertilization_recommendation, ) except Exception as exc: raise RuntimeError( diff --git a/irrigation/serializers.py b/irrigation/serializers.py index f0fd5a1..dbfa870 100644 --- a/irrigation/serializers.py +++ b/irrigation/serializers.py @@ -66,6 +66,9 @@ class IrrigationRecommendResponseSerializer(serializers.Serializer): class WaterStressRequestSerializer(serializers.Serializer): farm_uuid = serializers.CharField(required=False, help_text="شناسه یکتای مزرعه") 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): farm_uuid = attrs.get("farm_uuid") or attrs.get("sensor_uuid") diff --git a/irrigation/test_water_stress_api.py b/irrigation/test_water_stress_api.py index e5030b7..2777be6 100644 --- a/irrigation/test_water_stress_api.py +++ b/irrigation/test_water_stress_api.py @@ -30,7 +30,13 @@ class WaterStressApiTests(TestCase): response = self.client.post( "/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", ) diff --git a/irrigation/views.py b/irrigation/views.py index aa6c841..9d7e5ce 100644 --- a/irrigation/views.py +++ b/irrigation/views.py @@ -354,7 +354,13 @@ class WaterStressView(APIView): examples=[ OpenApiExample( "نمونه درخواست 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, ) ], @@ -368,7 +374,12 @@ class WaterStressView(APIView): ) service = apps.get_app_config("irrigation").get_water_stress_service() 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: return Response( {"code": 404, "msg": str(exc), "data": None},