This commit is contained in:
2026-04-27 18:02:26 +03:30
parent 7c2ec2144d
commit 190a668355
19 changed files with 193 additions and 825 deletions
+46 -58
View File
@@ -137,7 +137,7 @@ def _risk_level(score: float) -> str:
return "low"
def _build_risk_fallback(farm_details: dict[str, Any], plant_name: str | None, growth_stage: str | None) -> dict[str, Any]:
def _build_risk_context(farm_details: dict[str, Any], plant_name: str | None, growth_stage: str | None) -> dict[str, Any]:
risk = _weather_risk_summary(farm_details)
disease_level = _risk_level(risk["fungal_score"])
pest_level = _risk_level(risk["pest_score"])
@@ -199,24 +199,44 @@ def _build_risk_fallback(farm_details: dict[str, Any], plant_name: str | None, g
}
def _build_detection_fallback(images: list[dict[str, str]], plant_name: str | None) -> dict[str, Any]:
return {
"has_issue": False,
"category": "unknown",
"confidence": 0.2,
"severity": "low",
"summary": "تحلیل خودکار تصویر انجام نشد یا برای نتیجه قطعی داده کافی نبود.",
"detected_signs": [],
"possible_causes": ["کیفیت یا زاویه تصویر برای تشخیص کافی نیست"],
"immediate_actions": [
"یک تصویر نزدیک تر از برگ و ساقه ارسال شود.",
"در صورت مشاهده گسترش علائم، بازدید میدانی انجام شود.",
],
"reasoning": [
f"تعداد تصاویر دریافتی: {len(images)}",
f"نام گیاه: {plant_name or 'نامشخص'}",
],
def _validate_detection_result(parsed: dict[str, Any]) -> dict[str, Any]:
required_keys = {
"has_issue",
"category",
"confidence",
"severity",
"summary",
"detected_signs",
"possible_causes",
"immediate_actions",
"reasoning",
}
missing = [key for key in required_keys if key not in parsed]
if missing:
raise ValueError(
"Pest disease detection response is missing required fields: "
+ ", ".join(missing)
)
return parsed
def _validate_risk_result(parsed: dict[str, Any]) -> dict[str, Any]:
required_keys = {
"summary",
"forecast_window",
"overall_risk",
"disease_risk",
"pest_risk",
"key_drivers",
"recommended_actions",
}
missing = [key for key in required_keys if key not in parsed]
if missing:
raise ValueError(
"Pest disease risk response is missing required fields: "
+ ", ".join(missing)
)
return parsed
def _build_detection_messages(
@@ -320,29 +340,11 @@ def get_pest_disease_detection(
_complete_audit_log(audit_log, raw)
except Exception as exc:
logger.error("Pest disease detection failed for %s: %s", farm_uuid, exc)
fallback = _build_detection_fallback(normalized_images, resolved_plant_name)
_fail_audit_log(audit_log, str(exc), json.dumps(fallback, ensure_ascii=False))
return {
**fallback,
"farm_uuid": farm_uuid,
"knowledge_base": KB_NAME,
"tone_file": service.tone_file,
"raw_response": None,
}
_fail_audit_log(audit_log, str(exc))
raise RuntimeError(f"Pest disease detection failed for farm {farm_uuid}.") from exc
if not parsed:
parsed = _build_detection_fallback(normalized_images, resolved_plant_name)
parsed.setdefault("has_issue", parsed.get("category") not in {"no_issue", "unknown"})
parsed.setdefault("category", "unknown")
parsed.setdefault("confidence", 0.4)
parsed.setdefault("severity", "low")
parsed.setdefault("detected_signs", [])
parsed.setdefault("possible_causes", [])
parsed.setdefault("immediate_actions", [])
parsed.setdefault("reasoning", [])
parsed = _validate_detection_result(parsed)
parsed["farm_uuid"] = farm_uuid
parsed["knowledge_base"] = KB_NAME
parsed["tone_file"] = service.tone_file
parsed["raw_response"] = raw
return parsed
@@ -358,7 +360,7 @@ def get_pest_disease_risk(
service, client, model = _build_service_client(cfg)
farm_details = _load_farm_or_error(farm_uuid)
resolved_plant_name = plant_name or (farm_details.get("plants") or [{}])[0].get("name")
fallback = _build_risk_fallback(farm_details, resolved_plant_name, growth_stage)
risk_context = _build_risk_context(farm_details, resolved_plant_name, growth_stage)
user_query = query or "ریسک آفات و بیماری این مزرعه را برای چند روز آینده پیش بینی کن."
plant_text = build_plant_text(resolved_plant_name, growth_stage or "") if resolved_plant_name else ""
rag_context = build_rag_context(
@@ -374,7 +376,7 @@ def get_pest_disease_risk(
cfg=cfg,
query=user_query,
rag_context=rag_context,
structured_context=fallback,
structured_context=risk_context,
plant_text=plant_text or "",
)
audit_log = _create_audit_log(
@@ -392,24 +394,10 @@ def get_pest_disease_risk(
_complete_audit_log(audit_log, raw)
except Exception as exc:
logger.error("Pest disease risk prediction failed for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc), json.dumps(fallback, ensure_ascii=False))
fallback["farm_uuid"] = farm_uuid
fallback["knowledge_base"] = KB_NAME
fallback["tone_file"] = service.tone_file
fallback["raw_response"] = None
return fallback
_fail_audit_log(audit_log, str(exc))
raise RuntimeError(f"Pest disease risk prediction failed for farm {farm_uuid}.") from exc
if not parsed:
parsed = fallback
parsed.setdefault("summary", fallback["summary"])
parsed.setdefault("forecast_window", fallback["forecast_window"])
parsed.setdefault("overall_risk", fallback["overall_risk"])
parsed.setdefault("disease_risk", fallback["disease_risk"])
parsed.setdefault("pest_risk", fallback["pest_risk"])
parsed.setdefault("key_drivers", fallback["key_drivers"])
parsed.setdefault("recommended_actions", fallback["recommended_actions"])
parsed = _validate_risk_result(parsed)
parsed["farm_uuid"] = farm_uuid
parsed["knowledge_base"] = KB_NAME
parsed["tone_file"] = service.tone_file
parsed["raw_response"] = raw
return parsed