空运「货交」只记提货地址,不再当成起运地。舱位旧说法「释放」先问航线是否取消,陆运通关口岸示例改为友谊关。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-11 20:45:53 +08:00
co-authored by Cursor
parent b070a9b7a7
commit 185abbf45e
16 changed files with 533 additions and 49 deletions
+2 -2
View File
@@ -1068,7 +1068,7 @@ def apply_air_release(
send_group_text(
group_client_of(flow),
chat_id,
copy.air_release_fail(work_order_no=no, operator=name, reason="主账未开放释放"),
copy.air_release_fail(work_order_no=no, operator=name, reason="主账未开放取消舱位"),
)
return "release_unavailable"
from agent.policy.system_exception import (
@@ -1104,7 +1104,7 @@ def apply_air_release(
event=SystemExceptionEvent(
exception_type=TYPE_TMS,
step=STEP_TMS_RELEASE,
reason="空运释放舱位接口调用失败",
reason="空运取消舱位接口调用失败",
service="空运舱位",
error_code=why or "RELEASE_FAIL",
work_order_no=no,
+44 -16
View File
@@ -39,6 +39,24 @@ def _r(code: str, default: str, **kwargs: Any) -> str:
return render(code, default, **kwargs)
def _cabin_copy(code: str, default: str, **kwargs: Any) -> str:
"""
舱位话术。库里旧句仍写「释放」时,新发出的改用当前默认句。
先把填进去的原因、工单号抠掉再看骨架,避免 TMS 原文里的「释放」把新句子打回去。
已经发出的历史消息不在这里改。
"""
text = _r(code, default, **kwargs)
skeleton = text
for value in kwargs.values():
token = str(value or "")
if token:
skeleton = skeleton.replace(token, "")
if "释放" in skeleton:
return default
return text
# 空运补问:必填 7 项 + 非必填。报价日期默认当天,不进待补充。
_AIR_EXAMPLES = {
"起运港": "南京",
@@ -314,7 +332,7 @@ _LAND_EXAMPLES = {
"体积": "100CBM",
"毛重": "50kg",
"车型/数量": "9米6/1",
"通关口岸": "皇岗",
"通关口岸": "友谊关",
"运输类型": "国内运输拼车",
"线路类别": "国内长途/零担",
"运输分类": "急件",
@@ -2879,9 +2897,9 @@ def air_need_work_order() -> str:
def air_lock_need_work_order() -> str:
"""锁舱/释放必须自带工单号。后台 inquiring.air_lock_need_wo。"""
return _r(
return _cabin_copy(
"inquiring.air_lock_need_wo",
"锁舱或释放舱位请带上工单号,例如 WO202609160001锁舱。",
"锁舱或取消舱位请带上工单号,例如 WO202609160001取消舱位。",
)
@@ -3041,9 +3059,9 @@ def air_airline_only_quote() -> str:
def air_airline_only_cabin() -> str:
"""只有航线能锁舱或释放。后台 inquiring.air_airline_only_cabin。"""
return _r(
return _cabin_copy(
"inquiring.air_airline_only_cabin",
"只有岗位为「航线·空运」的人能锁舱或释放舱位。",
"只有岗位为「航线·空运」的人能锁舱或取消舱位。",
)
@@ -3264,13 +3282,13 @@ def air_release_ok(
) -> str:
"""空运释放成功。后台 inquiring.air_release_ok。"""
default = (
f"工单{work_order_no} 已通知TMS执行空运释放舱位成功\n"
f"工单{work_order_no} 已通知TMS执行空运取消舱位成功\n"
f"操作人:{operator}\n"
f"舱位选项:{option_no}\n"
f"执行编号:{exec_no}\n"
f"报价编号:{quote_no}"
)
return _r(
return _cabin_copy(
"inquiring.air_release_ok",
default,
ticket_no=work_order_no,
@@ -3285,12 +3303,12 @@ def air_release_fail(*, work_order_no: str, operator: str, reason: str) -> str:
"""空运释放失败。后台 inquiring.air_release_fail。"""
why = (reason or "").strip() or "TMS未返回原因"
default = (
f"工单{work_order_no} 空运释放舱位失败。\n"
f"工单{work_order_no} 空运取消舱位失败。\n"
f"操作人:{operator}\n"
f"释放舱位失败的原因:{why}\n"
f"取消舱位失败的原因:{why}\n"
"已通知IT运维排查,请稍等。"
)
return _r(
return _cabin_copy(
"inquiring.air_release_fail",
default,
ticket_no=work_order_no,
@@ -3302,9 +3320,9 @@ def air_release_fail(*, work_order_no: str, operator: str, reason: str) -> str:
def air_release_deal_blocked(work_order_no: str) -> str:
"""已成交不能释放舱位。后台 inquiring.air_release_deal_blocked。"""
no = (work_order_no or "").strip()
return _r(
return _cabin_copy(
"inquiring.air_release_deal_blocked",
f"释放舱位失败,工单{no}已成交。",
f"取消舱位失败,工单{no}已成交。",
ticket_no=no,
)
@@ -3312,9 +3330,9 @@ def air_release_deal_blocked(work_order_no: str) -> str:
def air_release_need_lock(work_order_no: str) -> str:
"""没有有效锁舱不能释放。后台 inquiring.air_release_need_lock。"""
no = (work_order_no or "").strip()
return _r(
return _cabin_copy(
"inquiring.air_release_need_lock",
f"工单{no}还没有有效锁舱,不能释放。",
f"工单{no}还没有有效锁舱,不能取消舱位。",
ticket_no=no,
)
@@ -3342,9 +3360,19 @@ SYS_EXC_RECOVERY_GROUP = "建群已恢复正常,感谢您的耐心等待。"
IMPACT_LLM_NO_TICKET = "尚未建单,不落后台工单"
IMPACT_LLM = "当前识别暂停,已有字段与工单状态保持不变"
IMPACT_TMS_QUOTE = "当前询价流程暂停,识别字段与原业务节点保持不变"
IMPACT_TMS_CABIN = "当前锁舱/释放暂停,工单状态保持不变"
IMPACT_TMS_CABIN = "当前锁舱/取消舱位暂停,工单状态保持不变"
IMPACT_TMS_LOCK = "锁舱失败,工单状态保持不变"
IMPACT_TMS_RELEASE = "释放舱位失败,工单状态保持不变"
IMPACT_TMS_RELEASE = "取消舱位失败,工单状态保持不变"
def air_release_ask() -> str:
"""航线说了和舱位有关的「释放」时先问,不直接取消。"""
return "是否需要取消舱位?"
def air_release_ask_dropped() -> str:
"""航线否认这次取消。不打 TMS。"""
return "已取消这次操作"
IMPACT_GROUP = "当前建群/加人暂停,已建群不新建第二个群"
+17 -3
View File
@@ -289,6 +289,15 @@ def _cabin(flow: Any, ticket: object, chat_id: str, sender_id: str, text: str, i
operator_name,
)
if intent == "ask":
if not is_airline(flow, sender_id):
send_group_text(group_client_of(flow), chat_id, copy.air_airline_only_cabin())
return "not_airline"
from agent.redis_coord.pending_cabin_cancel import save_pending_cabin_cancel
save_pending_cabin_cancel(chat_id, ticket_no(ticket))
send_group_text(group_client_of(flow), chat_id, copy.air_release_ask())
return "release_ask"
if intent == "release":
return apply_air_release(
flow=flow,
@@ -314,10 +323,15 @@ def _cabin(flow: Any, ticket: object, chat_id: str, sender_id: str, text: str, i
def _cabin_intent(text: str) -> str:
raw = text or ""
if "释放" in raw and "舱" in raw:
"""取消舱位马上办;和舱位有关的「释放」只提问。"""
from agent.routing.air_group_intent import cabin_release_kind
kind = cabin_release_kind(text or "")
if kind == "direct":
return "release"
if "锁舱" in raw:
if kind == "ask":
return "ask"
if "锁舱" in (text or ""):
return "lock"
return ""
@@ -888,7 +888,7 @@ def _resume_cabin_step(
is_ok=lambda x: bool((x or {}).get("ok")),
)
service = "空运舱位"
reason = "空运释放舱位接口调用失败"
reason = "空运取消舱位接口调用失败"
else:
# 与首次群锁舱对齐:重试必须带 operatorName / airline,否则 MANUAL 出站缺姓名。
locker = getattr(ledger, "lock_cabin", None)