UPDATE
This commit is contained in:
@@ -30,11 +30,33 @@ DEFAULT_STAGE_LABELS = {
|
|||||||
"maturity": "رسیدگی",
|
"maturity": "رسیدگی",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ENGINE_LABELS = {
|
||||||
|
"pcse": "موتور شبیه سازی PCSE",
|
||||||
|
"growth_projection": "موتور برآورد رشد",
|
||||||
|
}
|
||||||
|
|
||||||
|
MODEL_LABELS = {
|
||||||
|
"growth_projection_v1": "مدل برآورد رشد نسخه ۱",
|
||||||
|
"wofost": "مدل ووفوست",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class GrowthSimulationError(Exception):
|
class GrowthSimulationError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _fa_engine_name(name: str | None) -> str | None:
|
||||||
|
if not name:
|
||||||
|
return name
|
||||||
|
return ENGINE_LABELS.get(name, name)
|
||||||
|
|
||||||
|
|
||||||
|
def _fa_model_name(name: str | None) -> str | None:
|
||||||
|
if not name:
|
||||||
|
return name
|
||||||
|
return MODEL_LABELS.get(name, name)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GrowthSimulationContext:
|
class GrowthSimulationContext:
|
||||||
farm_uuid: str | None
|
farm_uuid: str | None
|
||||||
@@ -469,7 +491,7 @@ def _run_simulation(context: GrowthSimulationContext) -> tuple[dict[str, Any], i
|
|||||||
exc,
|
exc,
|
||||||
)
|
)
|
||||||
fallback_result = _run_projection_engine(context)
|
fallback_result = _run_projection_engine(context)
|
||||||
warning = f"Simulation engine failed, fallback projection used: {exc}"
|
warning = f"موتور شبیه سازی با خطا مواجه شد و برآورد جایگزین استفاده شد: {exc}"
|
||||||
return fallback_result, None, warning
|
return fallback_result, None, warning
|
||||||
|
|
||||||
|
|
||||||
@@ -622,8 +644,8 @@ def run_growth_simulation(payload: dict[str, Any], progress_callback=None) -> di
|
|||||||
return {
|
return {
|
||||||
"plant_name": context.plant_name,
|
"plant_name": context.plant_name,
|
||||||
"dynamic_parameters": context.dynamic_parameters,
|
"dynamic_parameters": context.dynamic_parameters,
|
||||||
"engine": simulation_result.get("engine"),
|
"engine": _fa_engine_name(simulation_result.get("engine")),
|
||||||
"model_name": simulation_result.get("model_name"),
|
"model_name": _fa_model_name(simulation_result.get("model_name")),
|
||||||
"scenario_id": scenario_id,
|
"scenario_id": scenario_id,
|
||||||
"simulation_warning": simulation_error,
|
"simulation_warning": simulation_error,
|
||||||
"summary_metrics": simulation_result.get("metrics", {}),
|
"summary_metrics": simulation_result.get("metrics", {}),
|
||||||
@@ -665,7 +687,7 @@ def _build_current_farm_chart_payload(
|
|||||||
"title": "تعداد برگ تخمینی",
|
"title": "تعداد برگ تخمینی",
|
||||||
"subtitle": "وضعیت فعلی",
|
"subtitle": "وضعیت فعلی",
|
||||||
"amount": round(_estimate_leaf_count(latest_lai), 2),
|
"amount": round(_estimate_leaf_count(latest_lai), 2),
|
||||||
"unit": "leaf",
|
"unit": "برگ",
|
||||||
"avatarColor": "success",
|
"avatarColor": "success",
|
||||||
"avatarIcon": "tabler-leaf",
|
"avatarIcon": "tabler-leaf",
|
||||||
},
|
},
|
||||||
@@ -673,7 +695,7 @@ def _build_current_farm_chart_payload(
|
|||||||
"title": "وزن بیوماس",
|
"title": "وزن بیوماس",
|
||||||
"subtitle": "برآورد فعلی",
|
"subtitle": "برآورد فعلی",
|
||||||
"amount": round(latest_biomass, 2),
|
"amount": round(latest_biomass, 2),
|
||||||
"unit": "kg/ha",
|
"unit": "کیلوگرم در هکتار",
|
||||||
"avatarColor": "primary",
|
"avatarColor": "primary",
|
||||||
"avatarIcon": "tabler-chart-bar",
|
"avatarIcon": "tabler-chart-bar",
|
||||||
},
|
},
|
||||||
@@ -681,7 +703,7 @@ def _build_current_farm_chart_payload(
|
|||||||
"title": "وزن محصول",
|
"title": "وزن محصول",
|
||||||
"subtitle": "برآورد فعلی",
|
"subtitle": "برآورد فعلی",
|
||||||
"amount": round(latest_storage, 2),
|
"amount": round(latest_storage, 2),
|
||||||
"unit": "kg/ha",
|
"unit": "کیلوگرم در هکتار",
|
||||||
"avatarColor": "warning",
|
"avatarColor": "warning",
|
||||||
"avatarIcon": "tabler-scale",
|
"avatarIcon": "tabler-scale",
|
||||||
},
|
},
|
||||||
@@ -698,8 +720,8 @@ def _build_current_farm_chart_payload(
|
|||||||
return {
|
return {
|
||||||
"farm_uuid": context.farm_uuid,
|
"farm_uuid": context.farm_uuid,
|
||||||
"plant_name": context.plant_name,
|
"plant_name": context.plant_name,
|
||||||
"engine": simulation_result.get("engine"),
|
"engine": _fa_engine_name(simulation_result.get("engine")),
|
||||||
"model_name": simulation_result.get("model_name"),
|
"model_name": _fa_model_name(simulation_result.get("model_name")),
|
||||||
"scenario_id": scenario_id,
|
"scenario_id": scenario_id,
|
||||||
"simulation_warning": simulation_warning,
|
"simulation_warning": simulation_warning,
|
||||||
"categories": categories,
|
"categories": categories,
|
||||||
@@ -731,7 +753,7 @@ class CurrentFarmChartSimulator:
|
|||||||
|
|
||||||
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) -> dict[str, Any]:
|
||||||
if not farm_uuid:
|
if not farm_uuid:
|
||||||
raise GrowthSimulationError("farm_uuid is required.")
|
raise GrowthSimulationError("ارسال farm_uuid الزامی است.")
|
||||||
|
|
||||||
resolved_plant_name = plant_name
|
resolved_plant_name = plant_name
|
||||||
if not resolved_plant_name:
|
if not resolved_plant_name:
|
||||||
@@ -741,10 +763,10 @@ class CurrentFarmChartSimulator:
|
|||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
if sensor is None:
|
if sensor is None:
|
||||||
raise GrowthSimulationError("Farm not found.")
|
raise GrowthSimulationError("مزرعه پیدا نشد.")
|
||||||
plant = sensor.plants.first()
|
plant = sensor.plants.first()
|
||||||
if plant is None:
|
if plant is None:
|
||||||
raise GrowthSimulationError("Plant not found for the selected farm.")
|
raise GrowthSimulationError("گیاهی برای مزرعه انتخاب شده پیدا نشد.")
|
||||||
resolved_plant_name = plant.name
|
resolved_plant_name = plant.name
|
||||||
|
|
||||||
context = build_growth_context(
|
context = build_growth_context(
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ def build_harvest_prediction_payload(*, farm_uuid: str, plant_name: str | None =
|
|||||||
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()
|
||||||
if sensor is None:
|
if sensor is None:
|
||||||
raise GrowthSimulationError("Farm not found.")
|
raise GrowthSimulationError("مزرعه پیدا نشد.")
|
||||||
plant = sensor.plants.first()
|
plant = sensor.plants.first()
|
||||||
if plant is None:
|
if plant is None:
|
||||||
raise GrowthSimulationError("Plant not found for the selected farm.")
|
raise GrowthSimulationError("گیاهی برای مزرعه انتخاب شده پیدا نشد.")
|
||||||
resolved_plant_name = plant.name
|
resolved_plant_name = plant.name
|
||||||
|
|
||||||
context = build_growth_context(
|
context = build_growth_context(
|
||||||
@@ -68,7 +68,7 @@ def build_harvest_prediction_payload(*, farm_uuid: str, plant_name: str | None =
|
|||||||
simulation_result, scenario_id, simulation_warning = _run_simulation(context)
|
simulation_result, scenario_id, simulation_warning = _run_simulation(context)
|
||||||
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("No simulation output available.")
|
raise GrowthSimulationError("هیچ خروجی شبیه سازی در دسترس نیست.")
|
||||||
|
|
||||||
profile = resolve_growth_profile(context.plant)
|
profile = resolve_growth_profile(context.plant)
|
||||||
required_gdd = _safe_float(profile.get("required_gdd_for_maturity"), 1200.0)
|
required_gdd = _safe_float(profile.get("required_gdd_for_maturity"), 1200.0)
|
||||||
|
|||||||
@@ -63,6 +63,15 @@ def _coerce_positive_int(value, default: int) -> int:
|
|||||||
return max(parsed, 1)
|
return max(parsed, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def _fa_task_status(status_name: str) -> str:
|
||||||
|
return {
|
||||||
|
"PENDING": "در انتظار",
|
||||||
|
"PROGRESS": "در حال پردازش",
|
||||||
|
"SUCCESS": "موفق",
|
||||||
|
"FAILURE": "ناموفق",
|
||||||
|
}.get(status_name, status_name)
|
||||||
|
|
||||||
|
|
||||||
class PlantGrowthSimulationView(APIView):
|
class PlantGrowthSimulationView(APIView):
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
tags=["Crop Simulation"],
|
tags=["Crop Simulation"],
|
||||||
@@ -153,7 +162,11 @@ class PlantGrowthSimulationStatusView(APIView):
|
|||||||
)
|
)
|
||||||
def get(self, request, task_id: str):
|
def get(self, request, task_id: str):
|
||||||
result = _get_async_result(task_id)
|
result = _get_async_result(task_id)
|
||||||
payload = {"task_id": task_id, "status": result.state}
|
payload = {
|
||||||
|
"task_id": task_id,
|
||||||
|
"status": result.state,
|
||||||
|
"status_fa": _fa_task_status(result.state),
|
||||||
|
}
|
||||||
|
|
||||||
if result.state == "PENDING":
|
if result.state == "PENDING":
|
||||||
payload["message"] = "تسک در صف یا یافت نشد."
|
payload["message"] = "تسک در صف یا یافت نشد."
|
||||||
@@ -181,7 +194,7 @@ class PlantGrowthSimulationStatusView(APIView):
|
|||||||
payload["error"] = str(result.result)
|
payload["error"] = str(result.result)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{"code": 200, "msg": "success", "data": payload},
|
{"code": 200, "msg": "موفق", "data": payload},
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -256,7 +269,7 @@ class CurrentFarmSimulationChartView(APIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{"code": 200, "msg": "success", "data": result},
|
{"code": 200, "msg": "موفق", "data": result},
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -313,7 +326,7 @@ class HarvestPredictionView(APIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{"code": 200, "msg": "success", "data": result},
|
{"code": 200, "msg": "موفق", "data": result},
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -355,7 +368,7 @@ class YieldPredictionView(APIView):
|
|||||||
{"code": 500, "msg": f"خطا در پیش بینی عملکرد: {exc}", "data": None},
|
{"code": 500, "msg": f"خطا در پیش بینی عملکرد: {exc}", "data": None},
|
||||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
)
|
)
|
||||||
return Response({"code": 200, "msg": "success", "data": result}, status=status.HTTP_200_OK)
|
return Response({"code": 200, "msg": "موفق", "data": result}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class YieldHarvestSummaryView(APIView):
|
class YieldHarvestSummaryView(APIView):
|
||||||
@@ -443,4 +456,4 @@ class YieldHarvestSummaryView(APIView):
|
|||||||
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),
|
||||||
)
|
)
|
||||||
return Response({"code": 200, "msg": "success", "data": payload}, status=status.HTTP_200_OK)
|
return Response({"code": 200, "msg": "موفق", "data": payload}, status=status.HTTP_200_OK)
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ from rag.services.yield_harvest import YieldHarvestRAGService
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
READINESS_STATUS_FA = {
|
||||||
|
"ready": "آماده",
|
||||||
|
"approaching": "نزدیک به آمادگی",
|
||||||
|
"monitoring": "نیازمند پایش",
|
||||||
|
"not_ready": "آماده نیست",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class YieldHarvestSummaryService:
|
class YieldHarvestSummaryService:
|
||||||
def get_summary(
|
def get_summary(
|
||||||
@@ -174,8 +181,8 @@ class YieldHarvestSummaryService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
fallback_description = (
|
fallback_description = (
|
||||||
f"Deterministic harvest forecast for {crop_name or 'the selected crop'} "
|
f"پیش بینی قطعی برداشت برای {crop_name or 'محصول انتخاب شده'} "
|
||||||
f"in season {season_year}."
|
f"در فصل زراعی {season_year}."
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -188,7 +195,7 @@ class YieldHarvestSummaryService:
|
|||||||
"optimal_window_start": result.get("optimalWindowStart"),
|
"optimal_window_start": result.get("optimalWindowStart"),
|
||||||
"optimal_window_end": result.get("optimalWindowEnd"),
|
"optimal_window_end": result.get("optimalWindowEnd"),
|
||||||
"description": result.get("description") or fallback_description,
|
"description": result.get("description") or fallback_description,
|
||||||
"descriptionSource": "deterministic",
|
"descriptionSource": "قطعی",
|
||||||
"field_conditions": {
|
"field_conditions": {
|
||||||
"soil_moisture": farm_context.get("recent_sensor_averages", {}).get("soil_moisture"),
|
"soil_moisture": farm_context.get("recent_sensor_averages", {}).get("soil_moisture"),
|
||||||
"soil_temperature": farm_context.get("recent_sensor_averages", {}).get("soil_temperature"),
|
"soil_temperature": farm_context.get("recent_sensor_averages", {}).get("soil_temperature"),
|
||||||
@@ -233,19 +240,19 @@ class YieldHarvestSummaryService:
|
|||||||
"season_year": season_year,
|
"season_year": season_year,
|
||||||
"series": [
|
"series": [
|
||||||
{
|
{
|
||||||
"name": "Predicted Yield",
|
"name": "عملکرد پیش بینی شده",
|
||||||
"type": "line",
|
"type": "line",
|
||||||
"data": yield_series,
|
"data": yield_series,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Biomass",
|
"name": "بیوماس",
|
||||||
"type": "area",
|
"type": "area",
|
||||||
"data": biomass_series,
|
"data": biomass_series,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"xAxis": {"type": "datetime"},
|
"xAxis": {"type": "datetime", "label": "تاریخ"},
|
||||||
"meta": {
|
"meta": {
|
||||||
"unit": "kg/ha",
|
"unit": "کیلوگرم در هکتار",
|
||||||
"simulation_engine": result.get("engine"),
|
"simulation_engine": result.get("engine"),
|
||||||
"simulation_model": result.get("model_name"),
|
"simulation_model": result.get("model_name"),
|
||||||
"scenario_id": result.get("scenario_id"),
|
"scenario_id": result.get("scenario_id"),
|
||||||
@@ -281,10 +288,10 @@ class YieldHarvestSummaryService:
|
|||||||
"days_until_harvest": days_until,
|
"days_until_harvest": days_until,
|
||||||
"current_dvs": round(pcse_dvs_stage, 4),
|
"current_dvs": round(pcse_dvs_stage, 4),
|
||||||
"summary": (
|
"summary": (
|
||||||
f"Operations are prioritized for {farm_context.get('crop_name') or 'the selected crop'} "
|
f"عملیات برداشت برای {farm_context.get('crop_name') or 'محصول انتخاب شده'} "
|
||||||
f"with {days_until} days remaining until the predicted harvest window."
|
f"با توجه به {days_until} روز باقی مانده تا بازه پیش بینی شده برداشت اولویت بندی شده است."
|
||||||
),
|
),
|
||||||
"rules_source": "deterministic_dvs_rules",
|
"rules_source": "قواعد_قطعی_DVS",
|
||||||
"field_context": {
|
"field_context": {
|
||||||
"soil_type": farm_context.get("soil", {}).get("soil_type"),
|
"soil_type": farm_context.get("soil", {}).get("soil_type"),
|
||||||
"soil_moisture": farm_context.get("recent_sensor_averages", {}).get("soil_moisture"),
|
"soil_moisture": farm_context.get("recent_sensor_averages", {}).get("soil_moisture"),
|
||||||
@@ -325,7 +332,7 @@ class YieldHarvestSummaryService:
|
|||||||
"farm_uuid": farm_uuid,
|
"farm_uuid": farm_uuid,
|
||||||
"crop_name": crop_name,
|
"crop_name": crop_name,
|
||||||
"season_year": season_year,
|
"season_year": season_year,
|
||||||
"title": "Season highlights",
|
"title": "خلاصه فصل",
|
||||||
# Left blank for narrative merge unless a non-LLM fallback is needed later.
|
# Left blank for narrative merge unless a non-LLM fallback is needed later.
|
||||||
"subtitle": "",
|
"subtitle": "",
|
||||||
"total_predicted_yield": total_predicted_yield,
|
"total_predicted_yield": total_predicted_yield,
|
||||||
@@ -357,7 +364,7 @@ class YieldHarvestSummaryService:
|
|||||||
"farm_uuid": farm_uuid,
|
"farm_uuid": farm_uuid,
|
||||||
"averageReadiness": None,
|
"averageReadiness": None,
|
||||||
"zones": [],
|
"zones": [],
|
||||||
"source": "ndvi_health_service",
|
"source": "سرویس_سلامت_NDVI",
|
||||||
}
|
}
|
||||||
|
|
||||||
location = sensor.center_location
|
location = sensor.center_location
|
||||||
@@ -398,7 +405,7 @@ class YieldHarvestSummaryService:
|
|||||||
zones.append(
|
zones.append(
|
||||||
{
|
{
|
||||||
"zoneId": f"zone-{zone_index}",
|
"zoneId": f"zone-{zone_index}",
|
||||||
"zoneLabel": f"Zone {zone_index}",
|
"zoneLabel": f"ناحیه {zone_index}",
|
||||||
"gridPosition": {"row": row_index, "col": col_index},
|
"gridPosition": {"row": row_index, "col": col_index},
|
||||||
"meanNdvi": cell_ndvi,
|
"meanNdvi": cell_ndvi,
|
||||||
"readiness": readiness,
|
"readiness": readiness,
|
||||||
@@ -413,7 +420,7 @@ class YieldHarvestSummaryService:
|
|||||||
zones.append(
|
zones.append(
|
||||||
{
|
{
|
||||||
"zoneId": "zone-center",
|
"zoneId": "zone-center",
|
||||||
"zoneLabel": "Center field zone",
|
"zoneLabel": "ناحیه مرکزی مزرعه",
|
||||||
"gridPosition": None,
|
"gridPosition": None,
|
||||||
"meanNdvi": latest_ndvi,
|
"meanNdvi": latest_ndvi,
|
||||||
"readiness": readiness,
|
"readiness": readiness,
|
||||||
@@ -441,7 +448,7 @@ class YieldHarvestSummaryService:
|
|||||||
"ndviTrend": ndvi_trend,
|
"ndviTrend": ndvi_trend,
|
||||||
"averageReadiness": average_readiness,
|
"averageReadiness": average_readiness,
|
||||||
"zones": zones,
|
"zones": zones,
|
||||||
"source": "ndvi_health_service",
|
"source": "سرویس_سلامت_NDVI",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _to_unix_timestamp(self, value: Any) -> int | None:
|
def _to_unix_timestamp(self, value: Any) -> int | None:
|
||||||
@@ -480,12 +487,12 @@ class YieldHarvestSummaryService:
|
|||||||
|
|
||||||
def _readiness_status(self, readiness: int) -> str:
|
def _readiness_status(self, readiness: int) -> str:
|
||||||
if readiness >= 80:
|
if readiness >= 80:
|
||||||
return "ready"
|
return READINESS_STATUS_FA["ready"]
|
||||||
if readiness >= 55:
|
if readiness >= 55:
|
||||||
return "approaching"
|
return READINESS_STATUS_FA["approaching"]
|
||||||
if readiness >= 30:
|
if readiness >= 30:
|
||||||
return "monitoring"
|
return READINESS_STATUS_FA["monitoring"]
|
||||||
return "not_ready"
|
return READINESS_STATUS_FA["not_ready"]
|
||||||
|
|
||||||
def _build_yield_quality_bands(
|
def _build_yield_quality_bands(
|
||||||
self,
|
self,
|
||||||
@@ -555,7 +562,7 @@ class YieldHarvestSummaryService:
|
|||||||
"farm_uuid": farm_uuid,
|
"farm_uuid": farm_uuid,
|
||||||
"crop_name": crop_name,
|
"crop_name": crop_name,
|
||||||
"season_year": season_year,
|
"season_year": season_year,
|
||||||
"source": "deterministic_grading_rules",
|
"source": "قواعد_قطعی_درجه_بندی",
|
||||||
"is_estimated": True,
|
"is_estimated": True,
|
||||||
"protein_content": {
|
"protein_content": {
|
||||||
"value": protein_content,
|
"value": protein_content,
|
||||||
@@ -568,7 +575,7 @@ class YieldHarvestSummaryService:
|
|||||||
"grade_distribution": grade_distribution,
|
"grade_distribution": grade_distribution,
|
||||||
"primary_quality_grade": primary_quality_grade,
|
"primary_quality_grade": primary_quality_grade,
|
||||||
"quality_score": quality_score,
|
"quality_score": quality_score,
|
||||||
"summary": f"Primary quality grade is {primary_quality_grade}.",
|
"summary": f"درجه کیفیت غالب محصول {primary_quality_grade} است.",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_estimated_revenue(
|
def _get_estimated_revenue(
|
||||||
@@ -682,7 +689,7 @@ class YieldHarvestSummaryService:
|
|||||||
return {
|
return {
|
||||||
"farm_uuid": farm_uuid,
|
"farm_uuid": farm_uuid,
|
||||||
"center_coordinates": None,
|
"center_coordinates": None,
|
||||||
"soil": {"provider": getattr(settings, "SOIL_DATA_PROVIDER", "unknown")},
|
"soil": {"provider": getattr(settings, "SOIL_DATA_PROVIDER", "نامشخص")},
|
||||||
"recent_sensor_averages": {},
|
"recent_sensor_averages": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,7 +719,7 @@ class YieldHarvestSummaryService:
|
|||||||
},
|
},
|
||||||
"farm_boundary": farm_details.get("center_location", {}).get("farm_boundary"),
|
"farm_boundary": farm_details.get("center_location", {}).get("farm_boundary"),
|
||||||
"soil": {
|
"soil": {
|
||||||
"provider": getattr(settings, "SOIL_DATA_PROVIDER", "unknown"),
|
"provider": getattr(settings, "SOIL_DATA_PROVIDER", "نامشخص"),
|
||||||
"soil_type": self._infer_soil_type(soil_details),
|
"soil_type": self._infer_soil_type(soil_details),
|
||||||
"resolved_metrics": soil_details,
|
"resolved_metrics": soil_details,
|
||||||
},
|
},
|
||||||
@@ -735,14 +742,14 @@ class YieldHarvestSummaryService:
|
|||||||
|
|
||||||
def _map_dvs_to_phase(self, dvs: float) -> tuple[str, str]:
|
def _map_dvs_to_phase(self, dvs: float) -> tuple[str, str]:
|
||||||
if dvs >= 2.0:
|
if dvs >= 2.0:
|
||||||
return "ready", "maturity"
|
return "آماده", "رسیدگی"
|
||||||
if dvs >= 1.7:
|
if dvs >= 1.7:
|
||||||
return "final_pre_harvest", "late_reproductive"
|
return "پیش_برداشت_نهایی", "زایشی_پایانی"
|
||||||
if dvs >= 1.2:
|
if dvs >= 1.2:
|
||||||
return "mid_pre_harvest", "grain_fill"
|
return "پیش_برداشت_میانی", "پرشدن_دانه"
|
||||||
if dvs >= 0.8:
|
if dvs >= 0.8:
|
||||||
return "monitoring", "reproductive_transition"
|
return "پایش", "گذار_زایشی"
|
||||||
return "early_pre_harvest", "vegetative"
|
return "پیش_برداشت_ابتدایی", "رشد_رویشی"
|
||||||
|
|
||||||
def _build_operations_steps(
|
def _build_operations_steps(
|
||||||
self,
|
self,
|
||||||
@@ -753,74 +760,74 @@ class YieldHarvestSummaryService:
|
|||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
field_ready = soil_moisture is None or soil_moisture <= 35.0
|
field_ready = soil_moisture is None or soil_moisture <= 35.0
|
||||||
|
|
||||||
if phase_name == "maturity":
|
if phase_name == "رسیدگی":
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"key": "desiccation",
|
"key": "desiccation",
|
||||||
"title": "Desiccation check",
|
"title": "بررسی خشک شدن محصول",
|
||||||
"status": "ready",
|
"status": "آماده",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": 0,
|
"estimated_days": 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "harvesting",
|
"key": "harvesting",
|
||||||
"title": "Harvesting",
|
"title": "برداشت",
|
||||||
"status": "ready" if field_ready else "watch_field_conditions",
|
"status": "آماده" if field_ready else "نیازمند بررسی شرایط مزرعه",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": max(min(days_until, 2), 0),
|
"estimated_days": max(min(days_until, 2), 0),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "transportation",
|
"key": "transportation",
|
||||||
"title": "Transportation",
|
"title": "انتقال محصول",
|
||||||
"status": "ready",
|
"status": "آماده",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": max(min(days_until + 1, 3), 1),
|
"estimated_days": max(min(days_until + 1, 3), 1),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if phase_name == "late_reproductive":
|
if phase_name == "زایشی_پایانی":
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"key": "equipment_check",
|
"key": "equipment_check",
|
||||||
"title": "Inspect harvest equipment",
|
"title": "بازبینی تجهیزات برداشت",
|
||||||
"status": "priority",
|
"status": "اولویت بالا",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": 1,
|
"estimated_days": 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "labor_plan",
|
"key": "labor_plan",
|
||||||
"title": "Confirm labor and transport plan",
|
"title": "نهایی کردن برنامه نیروی کار و حمل",
|
||||||
"status": "priority",
|
"status": "اولویت بالا",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": 2,
|
"estimated_days": 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "field_entry",
|
"key": "field_entry",
|
||||||
"title": "Verify field access and dry windows",
|
"title": "بررسی امکان ورود به مزرعه و بازه های خشک",
|
||||||
"status": "ready" if field_ready else "monitor",
|
"status": "آماده" if field_ready else "پایش",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": max(min(days_until, 5), 1),
|
"estimated_days": max(min(days_until, 5), 1),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if phase_name == "grain_fill":
|
if phase_name == "پرشدن_دانه":
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"key": "monitor_maturity",
|
"key": "monitor_maturity",
|
||||||
"title": "Track maturity and storage organ growth",
|
"title": "پایش رسیدگی و رشد اندام ذخیره ای",
|
||||||
"status": "active",
|
"status": "در حال انجام",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": 7,
|
"estimated_days": 7,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "review_readiness",
|
"key": "review_readiness",
|
||||||
"title": "Review zone readiness differences",
|
"title": "بررسی اختلاف آمادگی بین ناحیه ها",
|
||||||
"status": "active",
|
"status": "در حال انجام",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": 10,
|
"estimated_days": 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "prepare_logistics",
|
"key": "prepare_logistics",
|
||||||
"title": "Prepare harvest logistics plan",
|
"title": "آماده سازی برنامه لجستیک برداشت",
|
||||||
"status": "upcoming",
|
"status": "پیش رو",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": 14,
|
"estimated_days": 14,
|
||||||
},
|
},
|
||||||
@@ -828,22 +835,22 @@ class YieldHarvestSummaryService:
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"key": "weekly_monitoring",
|
"key": "weekly_monitoring",
|
||||||
"title": "Run weekly crop maturity checks",
|
"title": "پایش هفتگی رسیدگی محصول",
|
||||||
"status": "active",
|
"status": "در حال انجام",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": 14,
|
"estimated_days": 14,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "update_forecast",
|
"key": "update_forecast",
|
||||||
"title": "Refresh harvest timing forecast",
|
"title": "به روزرسانی پیش بینی زمان برداشت",
|
||||||
"status": "active",
|
"status": "در حال انجام",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": 10,
|
"estimated_days": 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "draft_operations",
|
"key": "draft_operations",
|
||||||
"title": "Draft harvest operation checklist",
|
"title": "تهیه چک لیست عملیات برداشت",
|
||||||
"status": "upcoming",
|
"status": "پیش رو",
|
||||||
"is_completed": False,
|
"is_completed": False,
|
||||||
"estimated_days": 21,
|
"estimated_days": 21,
|
||||||
},
|
},
|
||||||
@@ -856,12 +863,12 @@ class YieldHarvestSummaryService:
|
|||||||
if sand is None or clay is None or silt is None:
|
if sand is None or clay is None or silt is None:
|
||||||
return None
|
return None
|
||||||
if clay >= 40:
|
if clay >= 40:
|
||||||
return "clay"
|
return "رسی"
|
||||||
if sand >= 70 and clay <= 15:
|
if sand >= 70 and clay <= 15:
|
||||||
return "sandy"
|
return "شنی"
|
||||||
if silt >= 50 and clay < 27:
|
if silt >= 50 and clay < 27:
|
||||||
return "silty_loam"
|
return "سیلتی لوم"
|
||||||
return "loam"
|
return "لوم"
|
||||||
|
|
||||||
def _safe_float(self, value: Any, default: float | None = 0.0) -> float | None:
|
def _safe_float(self, value: Any, default: float | None = 0.0) -> float | None:
|
||||||
try:
|
try:
|
||||||
@@ -932,33 +939,33 @@ class YieldHarvestSummaryService:
|
|||||||
highlights = payload.get("season_highlights_card") or {}
|
highlights = payload.get("season_highlights_card") or {}
|
||||||
total_yield = highlights.get("total_predicted_yield")
|
total_yield = highlights.get("total_predicted_yield")
|
||||||
unit = highlights.get("yield_unit") or ""
|
unit = highlights.get("yield_unit") or ""
|
||||||
harvest_date = highlights.get("target_harvest_date") or "the predicted harvest window"
|
harvest_date = highlights.get("target_harvest_date") or "بازه پیش بینی شده برداشت"
|
||||||
if total_yield is None:
|
if total_yield is None:
|
||||||
return f"Harvest is targeted for {harvest_date} based on the deterministic season outlook."
|
return f"بر اساس چشم انداز قطعی فصل، برداشت برای {harvest_date} هدف گذاری شده است."
|
||||||
return f"Predicted yield is {total_yield} {unit} and harvest is targeted for {harvest_date}.".strip()
|
return f"عملکرد پیش بینی شده {total_yield} {unit} است و برداشت برای {harvest_date} هدف گذاری شده است.".strip()
|
||||||
|
|
||||||
def _default_yield_prediction_explanation(self, payload: dict[str, Any]) -> str:
|
def _default_yield_prediction_explanation(self, payload: dict[str, Any]) -> str:
|
||||||
yield_card = payload.get("yield_prediction") or {}
|
yield_card = payload.get("yield_prediction") or {}
|
||||||
predicted = yield_card.get("predicted_yield_tons")
|
predicted = yield_card.get("predicted_yield_tons")
|
||||||
unit = yield_card.get("unit") or ""
|
unit = yield_card.get("unit") or ""
|
||||||
if predicted is None:
|
if predicted is None:
|
||||||
return "Yield forecast is based on the deterministic crop simulation output."
|
return "پیش بینی عملکرد بر پایه خروجی قطعی شبیه سازی محصول محاسبه شده است."
|
||||||
return f"Yield forecast is based on the deterministic crop simulation and currently projects {predicted} {unit}.".strip()
|
return f"پیش بینی عملکرد بر پایه شبیه سازی قطعی محصول انجام شده و در حال حاضر مقدار {predicted} {unit} را نشان می دهد.".strip()
|
||||||
|
|
||||||
def _default_harvest_readiness_summary(self, payload: dict[str, Any]) -> str:
|
def _default_harvest_readiness_summary(self, payload: dict[str, Any]) -> str:
|
||||||
readiness = payload.get("harvest_readiness_zones") or {}
|
readiness = payload.get("harvest_readiness_zones") or {}
|
||||||
average = readiness.get("averageReadiness")
|
average = readiness.get("averageReadiness")
|
||||||
if average is None:
|
if average is None:
|
||||||
return "Harvest readiness is derived from the latest deterministic zone signals."
|
return "آمادگی برداشت از آخرین سیگنال های قطعی ناحیه ای استخراج شده است."
|
||||||
return f"Average harvest readiness is {average} based on the latest deterministic zone signals.".strip()
|
return f"میانگین آمادگی برداشت بر اساس آخرین سیگنال های قطعی ناحیه ای، {average} است.".strip()
|
||||||
|
|
||||||
def _default_operation_note(self, step: dict[str, Any]) -> str:
|
def _default_operation_note(self, step: dict[str, Any]) -> str:
|
||||||
title = step.get("title") or "This operation"
|
title = step.get("title") or "این عملیات"
|
||||||
status = step.get("status") or "planned"
|
status = step.get("status") or "برنامه ریزی شده"
|
||||||
estimate = step.get("estimated_days")
|
estimate = step.get("estimated_days")
|
||||||
if estimate is None:
|
if estimate is None:
|
||||||
return f"{title} is currently marked as {status}."
|
return f"وضعیت {title} در حال حاضر «{status}» ثبت شده است."
|
||||||
return f"{title} is {status} with an estimated timing of {estimate} days.".strip()
|
return f"{title} با وضعیت «{status}» و زمان بندی تقریبی {estimate} روز ثبت شده است.".strip()
|
||||||
|
|
||||||
def _resolve_service(self, *, getter_names: tuple[str, ...]) -> Any:
|
def _resolve_service(self, *, getter_names: tuple[str, ...]) -> Any:
|
||||||
app_config = apps.get_app_config("crop_simulation")
|
app_config = apps.get_app_config("crop_simulation")
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .growth_simulation import CurrentFarmChartSimulator, GrowthSimulationError
|
from .growth_simulation import (
|
||||||
|
CurrentFarmChartSimulator,
|
||||||
|
GrowthSimulationError,
|
||||||
|
_fa_engine_name,
|
||||||
|
_fa_model_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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) -> dict[str, Any]:
|
||||||
@@ -16,9 +21,9 @@ def build_yield_prediction_payload(*, farm_uuid: str, plant_name: str | None = N
|
|||||||
"predictedYieldTons": predicted_yield_tons,
|
"predictedYieldTons": predicted_yield_tons,
|
||||||
"predictedYieldRaw": round(yield_estimate, 2),
|
"predictedYieldRaw": round(yield_estimate, 2),
|
||||||
"unit": "تن",
|
"unit": "تن",
|
||||||
"sourceUnit": "kg/ha",
|
"sourceUnit": "کیلوگرم در هکتار",
|
||||||
"simulationEngine": result.get("engine"),
|
"simulationEngine": _fa_engine_name(result.get("engine")),
|
||||||
"simulationModel": result.get("model_name"),
|
"simulationModel": _fa_model_name(result.get("model_name")),
|
||||||
"scenarioId": result.get("scenario_id"),
|
"scenarioId": result.get("scenario_id"),
|
||||||
"simulationWarning": result.get("simulation_warning"),
|
"simulationWarning": result.get("simulation_warning"),
|
||||||
"supportingMetrics": result.get("metrics") or {},
|
"supportingMetrics": result.get("metrics") or {},
|
||||||
|
|||||||
Reference in New Issue
Block a user