fix: 已出号后新一轮询价不带上一单字段
点名海运时丢掉旧陆运补问草稿,避免补字段误发填写样例图。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -36,6 +36,44 @@ def _front_bookmark(sess) -> bool:
|
||||
return phase in {"clarify", "need_mode", "need_land_options", "wait_confirm"}
|
||||
|
||||
|
||||
def _drop_other_mode_drafts(*, owner: str, sender_id: str) -> None:
|
||||
"""
|
||||
本句已点名空运/海运/陆运:丢掉其它方式未出号的补问/选项草稿。
|
||||
|
||||
测服复现:旧陆运「选项未齐」书签还在,海运补「贸易条款 / 运输分类」会被陆运抢走,
|
||||
回「参数提取不全」并发送填写样例图。已出号会话留在工单号索引,不当当前草稿。
|
||||
"""
|
||||
kind = (owner or "").upper()
|
||||
sid = (sender_id or "").strip()
|
||||
if not sid or kind not in {"SEA", "AIR", "LAND"}:
|
||||
return
|
||||
land = get_land_text_flow()
|
||||
air = get_air_text_flow()
|
||||
sea = get_sea_text_flow()
|
||||
others = []
|
||||
if kind != "LAND":
|
||||
others.append(land)
|
||||
if kind != "AIR":
|
||||
others.append(air)
|
||||
if kind != "SEA":
|
||||
others.append(sea)
|
||||
for flow in others:
|
||||
sess = flow.session_of(sid)
|
||||
phase = (getattr(sess, "phase", None) or "").strip() if sess else ""
|
||||
if _front_bookmark(sess) or phase == "need_mode":
|
||||
flow.drop_current_bookmark(sid)
|
||||
logger.info(
|
||||
"text_inquiry 丢掉其它方式草稿 owner=%s dropped_phase=%s sender=%s",
|
||||
kind,
|
||||
phase or "-",
|
||||
sid,
|
||||
)
|
||||
if kind != "LAND":
|
||||
clearer = getattr(land, "_clear_parked_confirm", None)
|
||||
if callable(clearer):
|
||||
clearer(sid)
|
||||
|
||||
|
||||
def should_resume_other_as_inquiry(message: InboundMessage) -> bool:
|
||||
"""
|
||||
A 把补问短答标成闲聊时,仍续填当前询价。
|
||||
@@ -63,7 +101,9 @@ def _pick_text_flow(*, sender_id: str, text: str, injected_mode: str = "", prefe
|
||||
"""
|
||||
按当前书签或本句运输方式选空运/海运/陆运流程。
|
||||
|
||||
陆运核对卡上的确定优先于旧海运书签,避免「确定」被海运/空运吃掉。
|
||||
陆运核对卡上的「确定」优先于旧海运书签,避免「确定」被海运/空运吃掉。
|
||||
本句点名海运/空运/陆运时盖掉其它方式的旧补问草稿。
|
||||
核对卡优先于选项补问;海运/空运补问优先于旧陆运选项草稿,避免补贸易条款发陆运样例图。
|
||||
正在补问时,补问书签优先于已报价调价,避免第 3 次反问出不了转人工卡。
|
||||
A 已判成调价且当前不是补问:交给当前已报价工单,避免旧海运书签抢走。
|
||||
图片识图在 Worker 写下的补问书签,HTTP 必须从 Redis 读到,否则「托盘」会变成再问运输方式。
|
||||
@@ -82,11 +122,24 @@ def _pick_text_flow(*, sender_id: str, text: str, injected_mode: str = "", prefe
|
||||
return land
|
||||
sea = get_sea_text_flow()
|
||||
air = get_air_text_flow()
|
||||
spoken = (injected_mode or detect_transport_mode(text) or "").upper()
|
||||
# 本句点名运输方式:盖掉其它方式的旧补问,避免海运补字段走陆运样例图。
|
||||
if spoken in {"SEA", "AIR", "LAND"}:
|
||||
_drop_other_mode_drafts(owner=spoken, sender_id=sender_id)
|
||||
if spoken == "SEA":
|
||||
return sea
|
||||
if spoken == "LAND":
|
||||
return land
|
||||
return air
|
||||
sea_sess = sea.session_of(sender_id)
|
||||
land_sess = land.session_of(sender_id)
|
||||
air_sess = air.session_of(sender_id)
|
||||
# 补问/核对手稿优先于「已报价调价」,避免第 3 次反问被调价路由冲掉、出不了转人工卡。
|
||||
# 核对卡仍优先:销售正在改体积/点确定,不能被另一方式的补问草稿抢走。
|
||||
for sess, flow in ((land_sess, land), (air_sess, air), (sea_sess, sea)):
|
||||
if sess is not None and (sess.phase or "").strip() == "wait_confirm" and not is_handoff_frozen(sess):
|
||||
return flow
|
||||
# 补问书签:海运/空运优先于旧陆运选项草稿。陆运「确定」已在上面拦过。
|
||||
for sess, flow in ((sea_sess, sea), (air_sess, air), (land_sess, land)):
|
||||
if _front_bookmark(sess):
|
||||
return flow
|
||||
if prefer_quoted:
|
||||
@@ -97,11 +150,6 @@ def _pick_text_flow(*, sender_id: str, text: str, injected_mode: str = "", prefe
|
||||
return sea
|
||||
if land_sess and not is_handoff_frozen(land_sess) and (land_sess.business_line or "").upper() == "LAND":
|
||||
return land
|
||||
mode = (injected_mode or detect_transport_mode(text) or "").upper()
|
||||
if mode == "SEA":
|
||||
return sea
|
||||
if mode == "LAND":
|
||||
return land
|
||||
return air
|
||||
|
||||
|
||||
|
||||
@@ -395,6 +395,31 @@ class AirTextInquiryFlow:
|
||||
self._save(new_sess)
|
||||
return new_sess.phase or "clarify"
|
||||
|
||||
def _detach_ticketed_for_new_inquiry(self, sess: Optional[FlowSession]) -> Optional[FlowSession]:
|
||||
"""
|
||||
已经出过正式工单号:这一句按新一轮收字段,不带上一单。
|
||||
|
||||
测服 WO202609200012 出号后,下一句海运把旧贸易条款写进 WO202609200013。
|
||||
点按钮、未成交原因、调价已在前面处理。虚拟号还在补问,要接着填。
|
||||
"""
|
||||
if sess is None:
|
||||
return None
|
||||
from agent.policy.handoff_human import is_virtual_no
|
||||
|
||||
no = (sess.work_order_no or "").strip()
|
||||
phase = (sess.phase or "").strip()
|
||||
if not no or is_virtual_no(no):
|
||||
return sess
|
||||
if phase in {"clarify", "need_mode", "handoff_offer", "wait_lost_reason"}:
|
||||
return sess
|
||||
logger.info(
|
||||
"%s.new_over_old phase=%s wo=%s",
|
||||
self.bookmark_kind,
|
||||
phase or "-",
|
||||
no,
|
||||
)
|
||||
return None
|
||||
|
||||
def drop_current_bookmark(self, sender_id: str) -> Optional[FlowSession]:
|
||||
"""
|
||||
发图新开:丢掉当前书签,已出号会话仍留在 _by_ticket。
|
||||
@@ -691,6 +716,7 @@ class AirTextInquiryFlow:
|
||||
adjusted = try_private_adjust(self, sess, text, reply)
|
||||
if adjusted:
|
||||
return adjusted
|
||||
sess = self._detach_ticketed_for_new_inquiry(sess)
|
||||
|
||||
snap = extract_inquiry_snapshot(
|
||||
text,
|
||||
@@ -734,6 +760,8 @@ class AirTextInquiryFlow:
|
||||
if mode == "LAND":
|
||||
from agent.policy.land_text_flow import get_land_text_flow
|
||||
|
||||
# 本句已是陆运:丢掉空运补问草稿。
|
||||
self.drop_current_bookmark(sender_id)
|
||||
return get_land_text_flow().on_text(
|
||||
sender_id=sender_id,
|
||||
text=text,
|
||||
@@ -749,6 +777,7 @@ class AirTextInquiryFlow:
|
||||
# 海运交给海运 Owner,空运不再回「本轮先开通空运」
|
||||
from agent.policy.sea_text_flow import get_sea_text_flow
|
||||
|
||||
self.drop_current_bookmark(sender_id)
|
||||
return get_sea_text_flow().on_text(
|
||||
sender_id=sender_id,
|
||||
text=text,
|
||||
|
||||
@@ -169,6 +169,8 @@ class LandTextInquiryFlow(SeaTextInquiryFlow):
|
||||
if mode == "SEA":
|
||||
from agent.policy.sea_text_flow import get_sea_text_flow
|
||||
|
||||
# 抽成海运后丢掉陆运选项草稿,下一句补字段不能再发陆运样例图。
|
||||
self.drop_current_bookmark(sender_id)
|
||||
return get_sea_text_flow().on_text(
|
||||
sender_id=sender_id,
|
||||
text=text,
|
||||
@@ -182,6 +184,7 @@ class LandTextInquiryFlow(SeaTextInquiryFlow):
|
||||
)
|
||||
from agent.policy.air_text_flow import get_air_text_flow
|
||||
|
||||
self.drop_current_bookmark(sender_id)
|
||||
return get_air_text_flow().on_text(
|
||||
sender_id=sender_id,
|
||||
text=text,
|
||||
|
||||
@@ -99,6 +99,8 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
adjusted = try_private_adjust(self, sess, text, reply)
|
||||
if adjusted:
|
||||
return adjusted
|
||||
# 上一单已出号:新一轮从本句重收,禁止把旧贸易条款/分类带进下一张工单。
|
||||
sess = self._detach_ticketed_for_new_inquiry(sess)
|
||||
|
||||
snap = extract_inquiry_snapshot(
|
||||
text,
|
||||
@@ -139,6 +141,8 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
if mode == "LAND":
|
||||
from agent.policy.land_text_flow import get_land_text_flow
|
||||
|
||||
# 本句已是陆运:丢掉海运补问草稿,避免下一句又被海运吃掉。
|
||||
self.drop_current_bookmark(sender_id)
|
||||
return get_land_text_flow().on_text(
|
||||
sender_id=sender_id,
|
||||
text=text,
|
||||
@@ -153,6 +157,7 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
# 空运交给空运 Owner,避免海运流程半做空运
|
||||
from agent.policy.air_text_flow import get_air_text_flow
|
||||
|
||||
self.drop_current_bookmark(sender_id)
|
||||
return get_air_text_flow().on_text(
|
||||
sender_id=sender_id,
|
||||
text=text,
|
||||
@@ -165,9 +170,11 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
inbound_raw=inbound_raw,
|
||||
)
|
||||
|
||||
from agent.schema.sea_options import harvest_sea_class
|
||||
from agent.schema.sea_options import drop_invented_door_trade_terms, harvest_sea_class
|
||||
|
||||
facts = harvest_sea_class(text, facts)
|
||||
# 旧陆运/补问草稿可能把「门到门」塞进贸易条款;原话没写条款则丢掉。
|
||||
facts = drop_invented_door_trade_terms(text, facts)
|
||||
check = validate_required_fields(facts=facts, business_line="SEA")
|
||||
facts = dict(check["facts"])
|
||||
tms_ready = assemble_sea_query(facts)
|
||||
|
||||
@@ -59,6 +59,37 @@ def harvest_sea_class(text: str, current: dict[str, str] | None = None) -> dict[
|
||||
return merged
|
||||
|
||||
|
||||
# 运输分类用语,不能冒充贸易条款。销售原话写了「贸易条款」才保留。
|
||||
_DOOR_TRADE_TERMS = frozenset(
|
||||
{
|
||||
"门到门",
|
||||
"door to door",
|
||||
"door-to-door",
|
||||
"doortodoor",
|
||||
"d2d",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def drop_invented_door_trade_terms(text: str, facts: dict[str, str] | None = None) -> dict[str, str]:
|
||||
"""
|
||||
丢掉冒充贸易条款的「门到门」。
|
||||
|
||||
测服 WO202609200013:原话没写贸易条款,确认卡却出现「贸易条款:门到门」。
|
||||
门到门只是运输分类说法,不是 FOB/CIF 这类条款;原话没有「贸易条款」四字时不准收下。
|
||||
销售明确写了「贸易条款:门到门」则保留原文,不改六态。
|
||||
"""
|
||||
merged = dict(facts or {})
|
||||
trade = str(merged.get("贸易条款") or "").strip()
|
||||
if not trade:
|
||||
return merged
|
||||
if "贸易条款" in (text or ""):
|
||||
return merged
|
||||
if trade.lower().replace(" ", "") in {x.replace(" ", "") for x in _DOOR_TRADE_TERMS} or trade in _DOOR_TRADE_TERMS:
|
||||
merged.pop("贸易条款", None)
|
||||
return merged
|
||||
|
||||
|
||||
def sea_class_prompt() -> str:
|
||||
"""缺运输分类时只列两项,不再追问「其他是什么」。"""
|
||||
return "运输分类(必填):请回复 Port to Port 或 其他。"
|
||||
|
||||
Reference in New Issue
Block a user