This commit is contained in:
2026-04-30 02:10:15 +03:30
parent 46ba01e4cc
commit e2c70ec8b6
5 changed files with 141 additions and 94 deletions
+77 -70
View File
@@ -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")