确定运输方式前的多句询价带到所选方式并按该方式抽字段;成交已定后放舱不再推跟进,未成交原因不再被当成新询价。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -66,6 +66,38 @@ class FlowSession:
|
||||
virtual_work_order_no: str = ""
|
||||
|
||||
|
||||
def is_waiting_mode_session(sess) -> bool:
|
||||
"""还在问运输方式、业务线空着。这时抽字段要看前面几句,不只看点名的这一句。"""
|
||||
if sess is None:
|
||||
return False
|
||||
if (getattr(sess, "phase", None) or "").strip() != "need_mode":
|
||||
return False
|
||||
return not (getattr(sess, "business_line", None) or "").strip()
|
||||
|
||||
|
||||
def waiting_mode_source(sess, text: str) -> str:
|
||||
"""未定方式时返回前面全部原话加本句;已经在补问则只返回本句,避免旧句盖掉销售刚改的值。"""
|
||||
latest = text or ""
|
||||
if not is_waiting_mode_session(sess):
|
||||
return latest
|
||||
return stack_user_text(getattr(sess, "immutable_text", "") or "", latest)
|
||||
|
||||
|
||||
def stack_user_text(previous: str, latest: str) -> str:
|
||||
"""
|
||||
运输方式还没定时,把销售每一句原话接在一起。
|
||||
|
||||
不是只留上一句。同一句重复进来不追加。补问阶段不要用这个,避免把已定方式的旧句再拼回去改判。
|
||||
"""
|
||||
prev = (previous or "").strip()
|
||||
cur = (latest or "").strip()
|
||||
if not cur:
|
||||
return prev
|
||||
if not prev or cur == prev or prev.endswith("\n" + cur):
|
||||
return cur if not prev else prev
|
||||
return prev + "\n" + cur
|
||||
|
||||
|
||||
def _session_payload(sess: FlowSession) -> dict[str, Any]:
|
||||
"""书签转 JSON。tuple 改 list,方便 HTTP/Worker 共用。"""
|
||||
data = asdict(sess)
|
||||
@@ -512,6 +544,7 @@ class AirTextInquiryFlow:
|
||||
sender_id: str,
|
||||
work_order_no: str,
|
||||
reply: ReplyFn,
|
||||
text: str = "",
|
||||
) -> str:
|
||||
"""
|
||||
按工单号回到当时停点并重出对应卡片。
|
||||
@@ -530,6 +563,9 @@ class AirTextInquiryFlow:
|
||||
keep = None
|
||||
if keep:
|
||||
self._align_resume_phase(keep)
|
||||
closed = self._close_spoken_deal(keep, text, reply)
|
||||
if closed:
|
||||
return closed
|
||||
return self._replay_wait(keep, reply, source="session")
|
||||
getter = getattr(self._ledger, "get_for_agent", None)
|
||||
if not callable(getter):
|
||||
@@ -545,6 +581,9 @@ class AirTextInquiryFlow:
|
||||
sess = self._session_from_ledger(sender_id=sender_id, view=view)
|
||||
self._align_resume_phase(sess)
|
||||
self._save(sess)
|
||||
closed = self._close_spoken_deal(sess, text, reply)
|
||||
if closed:
|
||||
return closed
|
||||
return self._replay_wait(sess, reply, source="ledger")
|
||||
|
||||
def _session_from_ledger(self, *, sender_id: str, view: dict[str, Any]) -> FlowSession:
|
||||
@@ -659,8 +698,8 @@ class AirTextInquiryFlow:
|
||||
reply("报价单正在生成,请稍候。")
|
||||
return "wait_file"
|
||||
if sess.phase == "wait_deal":
|
||||
# 成交卡已经绑在报价文件那条出站上。这里再发一张独立卡,
|
||||
# 会在文件还在上传时被另一条出站线程抢走,销售又先看到成交跟进。
|
||||
# 成交卡已经绑在报价文件那条出站上。只续办、没说成交结果时不再另发一张卡。
|
||||
# 原话写了已成交/未成交/协商中时,由 _close_spoken_deal 先入账,不会走到这里。
|
||||
return "wait_deal"
|
||||
if sess.phase == "wait_lost_reason":
|
||||
reply(copy.deal_ack("未成交", sess.work_order_no))
|
||||
@@ -774,7 +813,8 @@ class AirTextInquiryFlow:
|
||||
|
||||
merged = dict(sess.facts)
|
||||
merged.update(facts)
|
||||
facts = harvest_oral_measures(text, merged)
|
||||
source = waiting_mode_source(sess, text)
|
||||
facts = harvest_oral_measures(source, merged)
|
||||
mode = mode or (sess.business_line or "").upper()
|
||||
if not mode:
|
||||
mode = detect_transport_mode(
|
||||
@@ -790,7 +830,10 @@ class AirTextInquiryFlow:
|
||||
phase="need_mode",
|
||||
business_line="",
|
||||
facts=facts,
|
||||
immutable_text=sess.immutable_text if sess and sess.immutable_text else text,
|
||||
immutable_text=stack_user_text(
|
||||
sess.immutable_text if sess else "",
|
||||
text,
|
||||
),
|
||||
)
|
||||
)
|
||||
return "need_mode"
|
||||
@@ -831,6 +874,11 @@ class AirTextInquiryFlow:
|
||||
reply(copy.ASK_TRANSPORT)
|
||||
return "need_mode"
|
||||
|
||||
if is_waiting_mode_session(sess):
|
||||
from agent.schema.field_validate import shape_facts_for_chosen_mode
|
||||
|
||||
# 刚选定空运:上文按空运字段收口,海运箱型不留在空运单上。
|
||||
facts = shape_facts_for_chosen_mode(facts, "AIR")
|
||||
check = validate_required_fields(facts=facts, business_line="AIR")
|
||||
facts = dict(check["facts"])
|
||||
tms_ready = assemble_air_query(facts)
|
||||
@@ -1631,22 +1679,46 @@ class AirTextInquiryFlow:
|
||||
card_meta: Optional[dict[str, Any]] = None,
|
||||
) -> str:
|
||||
"""
|
||||
成交跟进阶段口播未成交;同一句可带原因。
|
||||
成交跟进阶段口播已成交、未成交或协商中。未成交同一句可带原因。
|
||||
|
||||
点卡仍走 match_button。返回阶段名;不是未成交口播则空串。
|
||||
点卡仍走 match_button。原话写了别的工单号则不关当前单。不是口播则空串。
|
||||
"""
|
||||
from agent.policy.deal_outcome import looks_like_lost_outcome
|
||||
from agent.policy.deal_outcome import spoken_deal_status
|
||||
|
||||
if sess is None or not (text or "").strip():
|
||||
return ""
|
||||
allowed = sess.allowed or ()
|
||||
if copy.BTN_LOST not in allowed and sess.phase not in {"wait_deal", "wait_file"}:
|
||||
status = spoken_deal_status(text)
|
||||
if not status:
|
||||
return ""
|
||||
if not looks_like_lost_outcome(text):
|
||||
allowed = sess.allowed or ()
|
||||
if sess.phase not in {"wait_deal", "wait_file"} and copy.BTN_LOST not in allowed and copy.BTN_DEAL not in allowed:
|
||||
return ""
|
||||
if sess.phase not in {"wait_deal", "wait_file"} and status != "未成交":
|
||||
return ""
|
||||
named = copy.extract_work_order_no(text)
|
||||
if named and (sess.work_order_no or "").strip() and named.upper() != sess.work_order_no.strip().upper():
|
||||
return ""
|
||||
if sess.phase == "wait_file":
|
||||
self._promote_wait_deal(sess)
|
||||
return self._close(sess, reply, "未成交", card_meta=card_meta, source_text=text)
|
||||
return self._close(sess, reply, status, card_meta=card_meta, source_text=text)
|
||||
|
||||
def _close_spoken_deal(self, sess: FlowSession, text: str, reply: ReplyFn) -> str:
|
||||
"""
|
||||
续办原话已经写了成交结果:直接入账,不要静默停在成交跟进。
|
||||
|
||||
只在报价文件已出、正在等成交时生效。没写结果仍按原样重放,避免多发一张卡。
|
||||
"""
|
||||
from agent.policy.deal_outcome import spoken_deal_status
|
||||
|
||||
status = spoken_deal_status(text)
|
||||
if not status or sess.phase not in {"wait_deal", "wait_file"}:
|
||||
return ""
|
||||
named = copy.extract_work_order_no(text)
|
||||
if named and named.upper() != (sess.work_order_no or "").strip().upper():
|
||||
return ""
|
||||
if sess.phase == "wait_file":
|
||||
self._promote_wait_deal(sess)
|
||||
return self._close(sess, reply, status, source_text=text)
|
||||
|
||||
def _close(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user