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

停发填写样例图,同群只跟当前工单,重量体积统一为 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
+87 -8
View File
@@ -24,8 +24,8 @@ AIR_REQUIRED_FIELDS = (
("目的港", "目的地"),
("品名", "品名"),
("件数", "件数"),
("毛重", "重量"),
("体积", "体积"),
("毛重", "重量(KG)"),
("体积", "体积(CBM)"),
("包装方式", "包装类型"),
("报价日期", "报价日期"),
)
@@ -65,8 +65,8 @@ LAND_DISPLAY = {
"起运港": "始发站",
"目的港": "目的地",
"品名": "货物品名",
"毛重": "重量",
"体积": "体积",
"毛重": "重量(KG)",
"体积": "体积(CBM)",
"车型/数量": "车型/数量",
"通关口岸": "通关口岸",
"报价日期": "报价日期",
@@ -178,6 +178,20 @@ _RE_WEIGHT_WORD = re.compile(r"(?:毛重|重量)\s*[::]?\s*(\d+(?:\.\d+)?)")
_RE_VOLUME_WORD = re.compile(r"体积\s*[::]?\s*(\d+(?:\.\d+)?)")
# 销售原话里的包装词:当前句出现则覆盖旧值,散货也要收下
_PACK_WORDS = ("纸箱", "木箱", "托盘", "卡板", "散货")
# 销售口中的报价日期。内部键仍是报价日期。货好时间、提货地址不是这里。
QUOTE_DATE_SYNONYMS = (
"询价日期",
"走货日期",
"询价时间",
"走货时间",
"发货时间",
"发货日期",
"出货日期",
"出货时间",
"提货时间",
"提货日期",
)
# 报价日期:9月15日 / 2026-09-15;未写年用上海当年
_RE_DATE_YMD = re.compile(
r"(20\d{2})\s*[-/.年]\s*(\d{1,2})\s*[-/.月]\s*(\d{1,2})\s*[日号]?"
@@ -193,8 +207,10 @@ _ALIASES = {
"始发站": "起运港",
"货物品名": "品名",
"重量(KG)": "毛重",
"重量(KG)": "毛重",
"重量": "毛重",
"体积(CBM)": "体积",
"体积(CBM)": "体积",
"车型/数量": "车型/数量",
"车型数量": "车型/数量",
"数量": "数量",
@@ -220,6 +236,8 @@ _ALIASES = {
"贸易条款(非必填)": "贸易条款",
"起运港(非必填)": "起运港",
}
for _quote_alias in QUOTE_DATE_SYNONYMS:
_ALIASES[_quote_alias] = "报价日期"
def shanghai_today() -> str:
@@ -239,6 +257,7 @@ def normalize_facts(facts: dict[str, Any] | None) -> dict[str, str]:
for raw_key, raw_val in (facts or {}).items():
key = str(raw_key).strip()
key = re.sub(r"(必填)|(非必填)|\(必填\)|\(非必填\)", "", key).strip()
key = re.sub(r"(KG)|(CBM)|\(KG\)|\(CBM\)", "", key, flags=re.I).strip()
key = _ALIASES.get(key, key)
if raw_val is None:
continue
@@ -285,6 +304,32 @@ def _harvest_packaging(raw: str) -> str:
return last
def _quote_date_labeled_value(raw: str) -> str:
"""
原话里「报价日期」或同义词后面的值。
询价日期、走货日期、发货时间、提货日期等都算报价日期。
能写成 yyyy-MM-dd 就规范;「明天」这类留原文。
没有这些标签则空串,交给句子里的裸日期。
同一句出现多次,用最后一次,避免前面的货好时间把日期抢走。
"""
text = raw or ""
labels = ("报价日期",) + QUOTE_DATE_SYNONYMS
best_at = -1
best_val = ""
for label in labels:
pattern = re.compile(
rf"(?<![\u4e00-\u9fffA-Za-z0-9]){re.escape(label)}\s*[::]?\s*(\S+)"
)
for hit in pattern.finditer(text):
if hit.start() >= best_at:
best_at = hit.start()
best_val = hit.group(1).strip().strip(",,。;;、")
if not best_val:
return ""
return _harvest_quote_date(best_val) or best_val
def _harvest_quote_date(raw: str) -> str:
"""当前原话里的报价日期,规范成 yyyy-MM-dd;没有则空串。"""
from datetime import datetime
@@ -304,15 +349,26 @@ def _harvest_quote_date(raw: str) -> str:
return ""
_RE_CLARIFY_EXAMPLE = re.compile(r"^\s*[((]如\s*[::]")
def _without_clarify_examples(text: str) -> str:
"""补问清单里的「(如:托盘、散货)」整行丢掉,避免示例被当成已填。"""
return "\n".join(
line for line in (text or "").splitlines() if not _RE_CLARIFY_EXAMPLE.match(line)
)
def harvest_oral_measures(text: str, facts: dict[str, str] | None = None) -> dict[str, str]:
"""
从当前原话机读件数/重量/体积/包装/报价日期。
当前句写了的覆盖旧值:散货替换托盘,9月16日写入报价日期。
补问复制清单的示例行不参与机读。
副作用:无。不改主账。不猜港口。
"""
merged = dict(facts or {})
raw = text or ""
raw = _without_clarify_examples(text or "")
hit = _RE_PIECES.search(raw)
if hit:
merged["件数"] = f"{hit.group(1)}{hit.group(2)}"
@@ -334,9 +390,13 @@ def harvest_oral_measures(text: str, facts: dict[str, str] | None = None) -> dic
pack = _harvest_packaging(raw)
if pack:
merged["包装方式"] = pack
quote_date = _harvest_quote_date(raw)
if quote_date:
merged["报价日期"] = quote_date
labeled_date = _quote_date_labeled_value(raw)
if labeled_date:
merged["报价日期"] = labeled_date
else:
quote_date = _harvest_quote_date(raw)
if quote_date:
merged["报价日期"] = quote_date
from agent.schema.tms_air_query import harvest_named_air_route
return harvest_named_air_route(raw, merged)
@@ -405,6 +465,25 @@ def pick_collab_from_facts(
return out
def air_general_cargo_collab_defaults(facts: dict[str, str] | None) -> dict[str, str]:
"""
空运货物品名正好是「普货」时,含电、含磁缺省为否。
只补事实里还没写的项。已经写了「是」或「否」的不在这里改。
品名多一个字(如普货纸箱)不算普货,不默认。
调用方仍须遵守:群里已记下的协同值优先,本函数只给空项。
"""
src = normalize_facts(facts)
if (src.get("品名") or "").strip() != "普货":
return {}
out: dict[str, str] = {}
for key in AIR_COLLAB_FIELDS:
# 摘要占位「-」等于没写,不能挡住默认否。
if (src.get(key) or "").strip() in {"", "-", "—", "-"}:
out[key] = "否"
return out
def harvest_oral_collab(text: str) -> dict[str, str]:
"""
群里口语协同:含油/含电/含磁、货好时间、是否为危险品。