陆运选项不全时改为复制填空清单,方便销售按编号回填。

停发填写样例图,同群只跟当前工单,重量体积统一为 KG/CBM。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-23 17:10:13 +08:00
co-authored by Cursor
parent 2334524bb8
commit 378bb892b3
33 changed files with 1204 additions and 265 deletions
+92 -28
View File
@@ -26,23 +26,22 @@ from agent.schema.field_validate import (
)
from agent.schema.land_options import option_prompt as _land_option_prompt
from agent.schema.quote_adjust import total_price_label
from agent.schema.sea_options import sea_class_prompt
# 空运补问:必填 7 项 + 非必填。报价日期默认当天,不进待补充。
_AIR_EXAMPLES = {
"起运港": "南京",
"目的港": "吉隆坡",
"品名": "茶叶",
"件数": "10件",
"毛重": "100KGS",
"体积": "2CBM",
"包装方式": "托盘",
"件数": "10",
"毛重": "100",
"体积": "2",
"包装方式": "托盘、散货",
"报价日期": "2026-09-15",
"货源地": "东莞",
"货好时间": "随时可提",
"提货地址": "东莞仓库",
"收货地址": "马尼拉",
"贸易条款": "FOB",
"贸易条款": "DDP、DAP、CIF、CIP",
}
_AIR_CLARIFY = tuple(
(key, label, _AIR_EXAMPLES[key]) for key, label in AIR_REQUIRED_FIELDS
@@ -114,7 +113,15 @@ _CONTINUE_HINT = (
)
ASK_TRANSPORT = "请补充运输方式:海运 / 空运 / 陆运。"
ASK_TRANSPORT = (
"请回复数字或文字,告知我询价的运输方式:\n"
"1.陆运\n"
"2.海运\n"
"3.空运"
)
# 待补充清单:请销售整段复制,只在冒号后填值;示例行不算已填。
CLARIFY_PASTE_HINT = "请复制粘贴以下内容,通过回复数字或文字方式,告知我以下字段值:"
_PENDING_UNITS = {}
HANDOFF_ALREADY_COLLAB = "该工单已拉产品进群协同。"
HANDOFF_CLARIFY_REASON = "参数提取不全(连续 3 次反问仍不完整)"
HANDOFF_INTENT_REASON = "已识别您希望转人工处理"
@@ -179,10 +186,9 @@ ASK_LAND_SUBTYPE = _land_option_prompt(current={})
def land_sample_image_payload() -> dict[str, Any]:
"""
填写样例表图片出站。
填写样例表图片载荷。编号清单上线后不再主动出站,保留给旧调用。
走企微 image,不走文件气泡。路径随发布包,出站线程再读字节。
禁止在回调线程上传。
走企微 image,不走文件气泡。禁止在回调线程上传。
"""
from agent.schema.land_options import sample_image_path
@@ -195,7 +201,7 @@ def land_sample_image_payload() -> dict[str, Any]:
def land_sample_image_fallback() -> str:
"""图片失败时回文字样例,避免销售什么都看不到。"""
"""旧图失败兜底。新补问不再发图,一般不会走到这里。"""
from agent.schema.land_options import sample_fallback_text
return sample_fallback_text()
@@ -278,14 +284,40 @@ def _land_optional_keys(land_type: str) -> tuple[str, ...]:
return ()
def _pending_field_block(
index: int,
label: str,
example: str = "",
*,
optional: bool = False,
unit: str = "",
) -> str:
"""
待补充一项:编号 + 字段名 + 必填/单位,示例另起一行。
销售侧如:
1.件数(必填):
(如:10)
2.重量(KG)(必填):
(如:100)
复制回去时只认冒号后面的值;下一行「(如:…)」不算已填。
"""
mark = "(非必填)" if optional else "(必填)"
extra = f"({unit})" if (unit or "").strip() else ""
head = f"{index}.{label}{extra}{mark}:"
shown = (example or "").strip()
if shown:
return f"{head}\n(如:{shown})"
return head
def _land_field_line(key: str, value: str, *, required: bool, example: str = "") -> str:
"""销售侧一行:名称(必填/非必填):值或示例。"""
"""已识别一行:名称(必填/非必填):值。待补充走 _pending_field_block。"""
label = LAND_DISPLAY.get(key, key)
mark = "必填" if required else "非必填"
if value:
return f"{label}({mark}):{value}"
if example:
return f"{label}({mark}):(如:{example})"
_ = example
return f"{label}({mark}):"
@@ -350,11 +382,22 @@ def ask_land_fields(
if not pending:
return ""
lines.append("")
lines.append("待补充:")
lines.append(CLARIFY_PASTE_HINT)
seq = 0
for key in pending:
seq += 1
required = key not in optional
example = _LAND_EXAMPLES.get(key, "")
lines.append(_land_field_line(key, "", required=required, example=example))
label = LAND_DISPLAY.get(key, key)
lines.append(
_pending_field_block(
seq,
label,
example,
optional=not required,
unit=_PENDING_UNITS.get(key, ""),
)
)
return "\n".join(lines)
@@ -394,14 +437,13 @@ def _sea_known_value(facts: dict[str, str], key: str) -> str:
def _air_field_line(key: str, value: str, *, required: bool, example: str = "") -> str:
"""空运销售侧一行:名称(必填/非必填):值或示例。"""
"""空运核对卡一行:名称(必填/非必填):值。待补充走 _pending_field_block。"""
label = AIR_DISPLAY_LABEL(key)
mark = "必填" if required else "非必填"
text = (value or "").strip()
if text:
return f"{label}({mark}):{text}"
if example:
return f"{label}({mark}):(如:{example})"
_ = example
return f"{label}({mark}):"
@@ -440,7 +482,7 @@ def ask_clarify(
transport_mode: str = "空运",
) -> str:
"""
缺项补问:先回显已识别,再列待补充并给示例。
缺项补问:先回显已识别,再给可复制清单;示例另起一行,如「1.件数(必填):」下一行「(如:10)」。
报价日期写进已识别(销售说了用原话,没说用当天),不出现在待补充。
空运非必填一并列出,没填进待补充,不挡查价。
@@ -470,18 +512,28 @@ def ask_clarify(
if val:
lines.append(f"{label}:{val}")
lines.append("")
lines.append("待补充:")
lines.append(CLARIFY_PASTE_HINT)
any_missing = False
seq = 0
if "运输方式" in missing_set:
any_missing = True
lines.append("运输方式:(如:空运)")
seq += 1
lines.append(_pending_field_block(seq, "运输方式", "空运"))
for key, label, example in rows:
if key in missing_set:
any_missing = True
seq += 1
if sea and key == "运输分类":
lines.append(sea_class_prompt())
lines.append(_pending_field_block(seq, "运输分类", "Port to Port、其他"))
else:
lines.append(f"{label}:(如:{example})")
lines.append(
_pending_field_block(
seq,
label,
example,
unit=_PENDING_UNITS.get(key, ""),
)
)
if not sea:
optional_pending = [
(key, label, example)
@@ -490,8 +542,9 @@ def ask_clarify(
]
for key, label, example in optional_pending:
any_missing = True
seq += 1
shown = "起运港" if key == "起运港" else label
lines.append(f"{shown}(非必填):(如:{example})")
lines.append(_pending_field_block(seq, shown, example, optional=True))
if not any_missing:
return ""
return "\n".join(lines)
@@ -507,8 +560,10 @@ def ask_missing(display_names: Iterable[str]) -> str:
"毛重": "毛重",
"重量": "毛重",
"重量(KG)": "毛重",
"重量(KG)": "毛重",
"体积": "体积",
"体积(CBM)": "体积",
"体积(CBM)": "体积",
"包装类型": "包装方式",
"包装方式": "包装方式",
"货物数量": "货量",
@@ -610,8 +665,8 @@ def inquiry_field_lines(
f"目的地:{dest}",
f"品名:{cargo}",
f"件数:{_with_unit(str(facts.get('件数') or ''), '件')}",
f"重量:{_with_unit(str(facts.get('毛重') or ''), 'KGS')}",
f"体积:{_with_unit(str(facts.get('体积') or ''), 'CBM')}",
f"重量(KG):{_with_unit(str(facts.get('毛重') or ''), 'KGS')}",
f"体积(CBM):{_with_unit(str(facts.get('体积') or ''), 'CBM')}",
f"包装类型:{pack}",
f"报价日期:{quote_date}",
]
@@ -2601,8 +2656,17 @@ def air_group_brief(
lines.append("")
lines.append("协同补充字段:")
collab = dict(collab_facts or {})
from agent.schema.field_validate import air_general_cargo_collab_defaults
# 品名是普货时,摘要里不能再把没写的含电/含磁显示成横杠。
for key, val in air_general_cargo_collab_defaults(facts).items():
current = (collab.get(key) or "").strip()
if current in {"", "-", "—", "-"}:
collab[key] = val
for key in AIR_COLLAB_FIELDS:
val = (collab.get(key) or "").strip() or "-"
val = (collab.get(key) or "").strip()
if val in {"", "-", "—", "-"}:
val = "-"
lines.append(f"{key}:{val}")
tms, manual, adjust = _split_brief_quotes(
quote=quote,