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