feat: 事件异常主账起停等待接口

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-21 18:21:03 +08:00
co-authored by Cursor
parent 9e27bada94
commit 1bab19f1a2
4 changed files with 241 additions and 0 deletions
+46
View File
@@ -477,3 +477,49 @@ class HttpLedger:
if isinstance(rows, list):
return [dict(x) for x in rows if isinstance(x, dict)]
return []
def start_event_wait(self, *, work_order_no: str, wait_kind: str) -> dict[str, Any]:
out = self._post(
"/inquiry/agent/ticket/startEventWait",
{"workOrderNo": work_order_no, "waitKind": wait_kind},
)
if out.get("ok") is False:
return out
out.setdefault("ok", True)
return out
def clear_event_wait(self, *, work_order_no: str) -> dict[str, Any]:
out = self._post(
"/inquiry/agent/ticket/clearEventWait",
{"workOrderNo": work_order_no},
)
if out.get("ok") is False:
return out
out.setdefault("ok", True)
return out
def list_event_waits(self) -> list[dict[str, Any]]:
out = self._post("/inquiry/agent/ticket/listEventWaits", {})
rows = out.get("result") if isinstance(out.get("result"), list) else None
if rows is None:
rows = out.get("tickets") if isinstance(out.get("tickets"), list) else []
return [dict(x) for x in rows if isinstance(x, dict)]
def record_event_exception(self, *, work_order_no: str, event_exception: str) -> dict[str, Any]:
out = self._post(
"/inquiry/agent/ticket/recordEventException",
{"workOrderNo": work_order_no, "eventException": event_exception},
)
if out.get("ok") is False:
return out
out.setdefault("ok", True)
return out
def get_timeout_by_code(self, *, timeout_code: str) -> dict[str, Any]:
out = self._post(
"/inquiry/agent/responseTimeout/byCode",
{"timeoutCode": timeout_code},
)
if "found" not in out:
return {"found": False, "timeoutCode": timeout_code}
return out
+121
View File
@@ -44,6 +44,10 @@ class MemoryTicket:
lost_reason: str = ""
next_followup_at: str = ""
deal_notify_summary: str = ""
event_exception: str = ""
event_wait_kind: str = ""
event_wait_started_at: str = ""
event_wait_alerted: int = 0
system_exception: str = ""
system_exception_reason: str = ""
system_exception_step: str = ""
@@ -77,6 +81,7 @@ class MemoryLedger:
self._staff_by_wecom: dict[str, dict[str, Any]] = {}
self._staff_by_name: dict[str, dict[str, Any]] = {}
self._exception_notify_users: list[dict[str, Any]] = []
self._timeouts: dict[str, float] = {}
def _next_no(self) -> str:
self._seq += 1
@@ -727,3 +732,119 @@ class MemoryLedger:
"releaseId": f"CA-{no}-RLSE",
"tmsRequestId": f"TMS-RLSE-{no}",
}
def start_event_wait(self, *, work_order_no: str, wait_kind: str) -> dict[str, Any]:
"""开始盯等待。同种类不重置起点。不改六态。"""
no = (work_order_no or "").strip()
kind = (wait_kind or "").strip()
with self._lock:
ticket = self._tickets.get(no)
if ticket is None:
return {"ok": False, "error": "not_found"}
if ticket.status in {"已成交", "已关闭", "转人工"} or not kind:
return {"ok": False, "error": "not_watchable", "status": ticket.status}
if ticket.event_wait_kind == kind and ticket.event_wait_started_at:
return {"ok": True, "work_order_no": no, "eventWaitKind": kind}
ticket.event_wait_kind = kind
ticket.event_wait_started_at = datetime.now(_SHANGHAI).strftime("%Y-%m-%d %H:%M:%S")
ticket.event_wait_alerted = 0
return {"ok": True, "work_order_no": no, "eventWaitKind": kind}
def clear_event_wait(self, *, work_order_no: str) -> dict[str, Any]:
no = (work_order_no or "").strip()
with self._lock:
ticket = self._tickets.get(no)
if ticket is None:
return {"ok": False, "error": "not_found"}
ticket.event_wait_kind = ""
ticket.event_wait_started_at = ""
ticket.event_wait_alerted = 0
return {"ok": True, "work_order_no": no}
def list_event_waits(self) -> list[dict[str, Any]]:
out: list[dict[str, Any]] = []
with self._lock:
for ticket in self._tickets.values():
if not (ticket.event_wait_kind or "").strip():
continue
if int(ticket.event_wait_alerted or 0) != 0:
continue
if ticket.status in {"已成交", "已关闭", "转人工"}:
continue
watchers: list[dict[str, Any]] = []
seen: set[str] = set()
sales_id = (ticket.sales_wecom_id or "").strip()
if sales_id:
row = dict(self._staff_by_wecom.get(sales_id) or {})
watchers.append(
{
"wecomId": sales_id,
"name": str(row.get("name") or ticket.sales_wecom_id),
"roleCode": str(row.get("roleCode") or row.get("role_code") or "sales"),
"notifyEventException": row.get("notifyEventException", 0),
}
)
seen.add(sales_id)
for uid in ticket.collab_member_ids:
key = str(uid).strip()
if not key or key in seen:
continue
row = dict(self._staff_by_wecom.get(key) or {})
if not row:
continue
watchers.append(
{
"wecomId": key,
"name": str(row.get("name") or key),
"roleCode": str(row.get("roleCode") or row.get("role_code") or ""),
"notifyEventException": row.get("notifyEventException", 0),
}
)
seen.add(key)
out.append(
{
"work_order_no": ticket.work_order_no,
"workOrderNo": ticket.work_order_no,
"status": ticket.status,
"business_line": ticket.business_line,
"businessLine": ticket.business_line,
"event_wait_kind": ticket.event_wait_kind,
"eventWaitKind": ticket.event_wait_kind,
"event_wait_started_at": ticket.event_wait_started_at,
"eventWaitStartedAt": ticket.event_wait_started_at,
"sales_wecom_id": ticket.sales_wecom_id,
"salesWecomId": ticket.sales_wecom_id,
"sales_name": str(self._staff_by_wecom.get(sales_id, {}).get("name") or "销售"),
"collab_chat_id": ticket.collab_chat_id,
"collabChatId": ticket.collab_chat_id,
"collab_member_ids": list(ticket.collab_member_ids),
"facts": dict(ticket.facts),
"eventException": ticket.event_exception,
"watchers": watchers,
}
)
return out
def record_event_exception(self, *, work_order_no: str, event_exception: str) -> dict[str, Any]:
from agent.policy.event_exception import append_summary
no = (work_order_no or "").strip()
with self._lock:
ticket = self._tickets.get(no)
if ticket is None:
return {"ok": False, "error": "not_found"}
ticket.event_exception = append_summary(ticket.event_exception, event_exception)
ticket.event_wait_alerted = 1
return {
"ok": True,
"work_order_no": no,
"eventException": ticket.event_exception,
}
def get_timeout_by_code(self, *, timeout_code: str) -> dict[str, Any]:
code = (timeout_code or "").strip()
hours = self._timeouts.get(code)
if hours is None:
return {"found": False, "timeoutCode": code}
return {"found": True, "timeoutCode": code, "timeoutHours": hours}