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
+34 -72
View File
@@ -142,60 +142,32 @@ def _build_structured_context(farm_uuid: str) -> tuple[dict[str, Any], dict[str,
return context, structured
def _build_fallback_notifications(tracker: dict[str, Any], endpoint: str) -> list[dict[str, Any]]:
notifications: list[dict[str, Any]] = []
for alert in tracker.get("alerts", [])[:5]:
notifications.append(
{
"level": _severity_to_level(alert.get("severity")),
"title": alert.get("title") or "هشدار مزرعه",
"message": alert.get("summary") or alert.get("explanation") or "",
"suggested_action": alert.get("recommended_action") or "",
"source_alert_id": _alert_identifier(alert),
"source_metric_type": alert.get("metric_type") or "",
"payload": {
"endpoint": endpoint,
"alert": alert,
},
}
def _validate_tracker_response(payload: dict[str, Any]) -> dict[str, Any]:
required_keys = {"headline", "overview", "status_level", "notifications"}
missing = [key for key in required_keys if key not in payload]
if missing:
raise ValueError(
"Farm alerts tracker response is missing required fields: "
+ ", ".join(missing)
)
return notifications
if not isinstance(payload.get("notifications"), list):
raise ValueError("Farm alerts tracker notifications must be a list.")
return payload
def _build_fallback_tracker_response(tracker: dict[str, Any]) -> dict[str, Any]:
top_alert = tracker.get("mostCriticalIssue") or {}
status_level = _severity_to_level(top_alert.get("severity")) if top_alert else FarmAlertNotification.LEVEL_INFO
if tracker.get("totalAlerts", 0) <= 0:
overview = "در حال حاضر هشدار فعالی برای مزرعه شناسایی نشده است."
else:
overview = top_alert.get("summary") or "چند هشدار فعال برای مزرعه شناسایی شده است."
return {
"headline": "ارزیابی فعلی هشدارهای مزرعه",
"overview": overview,
"status_level": status_level,
"notifications": _build_fallback_notifications(tracker, FarmAlertNotification.ENDPOINT_TRACKER),
}
def _build_fallback_timeline_response(tracker: dict[str, Any]) -> dict[str, Any]:
timeline = []
for alert in tracker.get("alerts", [])[:6]:
timeline.append(
{
"timestamp": alert.get("timestamp"),
"level": _severity_to_level(alert.get("severity")),
"title": alert.get("title") or "رویداد هشدار",
"description": alert.get("explanation") or alert.get("summary") or "",
"source_alert_id": _alert_identifier(alert),
"source_metric_type": alert.get("metric_type") or "",
}
def _validate_timeline_response(payload: dict[str, Any]) -> dict[str, Any]:
required_keys = {"headline", "overview", "timeline", "notifications"}
missing = [key for key in required_keys if key not in payload]
if missing:
raise ValueError(
"Farm alerts timeline response is missing required fields: "
+ ", ".join(missing)
)
return {
"headline": "خط زمانی هشدارهای مزرعه",
"overview": "timeline بر اساس هشدارهای محاسبه شده مزرعه ساخته شد.",
"timeline": timeline,
"notifications": _build_fallback_notifications(tracker, FarmAlertNotification.ENDPOINT_TIMELINE),
}
if not isinstance(payload.get("timeline"), list):
raise ValueError("Farm alerts timeline must be a list.")
if not isinstance(payload.get("notifications"), list):
raise ValueError("Farm alerts timeline notifications must be a list.")
return payload
def _notification_fingerprint(
@@ -354,12 +326,14 @@ def _llm_response(
response = client.chat.completions.create(model=model, messages=messages)
raw = response.choices[0].message.content.strip()
parsed = _clean_json_response(raw)
if not parsed:
raise ValueError("farm_alerts LLM returned an empty or invalid JSON payload.")
_complete_audit_log(audit_log, raw)
return parsed, raw, service.tone_file or ""
except Exception as exc:
logger.error("farm_alerts llm error for %s: %s", farm_uuid, exc)
_fail_audit_log(audit_log, str(exc))
return {}, "", service.tone_file or ""
raise RuntimeError(f"Farm alerts generation failed for farm {farm_uuid}.") from exc
def get_farm_alerts_tracker(*, farm_uuid: str, query: str | None = None) -> dict[str, Any]:
@@ -373,12 +347,9 @@ def get_farm_alerts_tracker(*, farm_uuid: str, query: str | None = None) -> dict
query=user_query,
structured_context=structured_context,
)
if not llm_result:
llm_result = _build_fallback_tracker_response(tracker)
llm_result = _validate_tracker_response(llm_result)
notifications_input = llm_result.get("notifications")
if not isinstance(notifications_input, list):
notifications_input = _build_fallback_notifications(tracker, FarmAlertNotification.ENDPOINT_TRACKER)
notifications_input = llm_result["notifications"]
saved_notifications = _save_notifications(
farm_uuid=farm_uuid,
endpoint=FarmAlertNotification.ENDPOINT_TRACKER,
@@ -387,11 +358,9 @@ def get_farm_alerts_tracker(*, farm_uuid: str, query: str | None = None) -> dict
return {
"farm_uuid": farm_uuid,
"service_id": SERVICE_ID,
"knowledge_base": KB_NAME,
"tone_file": tone_file,
"tracker": tracker,
"headline": llm_result.get("headline") or "ارزیابی فعلی هشدارهای مزرعه",
"overview": llm_result.get("overview") or "",
"headline": llm_result["headline"],
"overview": llm_result["overview"],
"status_level": _normalize_level(llm_result.get("status_level")),
"notifications": [_serialize_notification(item) for item in saved_notifications],
"raw_llm_response": raw_response or None,
@@ -410,15 +379,10 @@ def get_farm_alerts_timeline(*, farm_uuid: str, query: str | None = None) -> dic
query=user_query,
structured_context=structured_context,
)
if not llm_result:
llm_result = _build_fallback_timeline_response(tracker)
llm_result = _validate_timeline_response(llm_result)
timeline = llm_result.get("timeline")
if not isinstance(timeline, list):
timeline = _build_fallback_timeline_response(tracker).get("timeline", [])
notifications_input = llm_result.get("notifications")
if not isinstance(notifications_input, list):
notifications_input = _build_fallback_notifications(tracker, FarmAlertNotification.ENDPOINT_TIMELINE)
timeline = llm_result["timeline"]
notifications_input = llm_result["notifications"]
saved_notifications = _save_notifications(
farm_uuid=farm_uuid,
@@ -428,11 +392,9 @@ def get_farm_alerts_timeline(*, farm_uuid: str, query: str | None = None) -> dic
return {
"farm_uuid": farm_uuid,
"service_id": SERVICE_ID,
"knowledge_base": KB_NAME,
"tone_file": tone_file,
"tracker": tracker,
"headline": llm_result.get("headline") or "خط زمانی هشدارهای مزرعه",
"overview": llm_result.get("overview") or "",
"headline": llm_result["headline"],
"overview": llm_result["overview"],
"timeline": timeline,
"notifications": [_serialize_notification(item) for item in saved_notifications],
"raw_llm_response": raw_response or None,