海运补问保留贸易条款;陆运多车型价表按列回填,车型码不进起始地

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-10-29 10:53:30 +08:00
co-authored by Cursor
parent 56ba8e93b5
commit eca0a7ad93
10 changed files with 809 additions and 18 deletions
+53 -7
View File
@@ -169,15 +169,35 @@ ALL_COLLAB_KEYS = tuple(
# 口语里已经带上 TMS 文档规定的单位时,补进空字段。
# 只认「数字+单位」,不猜港口,不把长宽高换算成立方。
_RE_PIECES = re.compile(r"(?<![\d.])(\d+)\s*(件|pcs)\b", re.I)
_RE_WEIGHT = re.compile(r"(?<![\d.])(\d+(?:\.\d+)?)\s*(kgs|kg|公斤|千克)\b", re.I)
# 单位后不用 Unicode \b:CBM/KG 紧贴中文时 \w 含汉字,\b 不成立会整段漏抽。
_RE_PIECES = re.compile(r"(?<![\d.])(\d+)\s*(件|pcs)(?![A-Za-z0-9])", re.I)
_RE_WEIGHT = re.compile(
r"(?<![\d.])(\d+(?:\.\d+)?)\s*(kgs|kg|公斤|千克)(?![A-Za-z0-9])",
re.I,
)
_RE_VOLUME = re.compile(
r"(?<![\d.])(\d+(?:\.\d+)?)\s*(cbm|立方米|立方|m3|m³)\b",
r"(?<![\d.])(\d+(?:\.\d+)?)\s*(cbm|立方米|立方|m3|m³)(?![A-Za-z0-9])",
re.I,
)
_RE_VOLUME_FANG = re.compile(r"(?<![\d.])(\d+(?:\.\d+)?)\s*方(?!向)")
_RE_WEIGHT_WORD = re.compile(r"(?:毛重|重量)\s*[::]?\s*(\d+(?:\.\d+)?)")
_RE_VOLUME_WORD = re.compile(r"体积\s*[::]?\s*(\d+(?:\.\d+)?)")
# 销售常把小数点两侧空开,或误用全角点/中文句号:578. 5KG、1. 03CBM。
# 只在后面跟重量/体积单位时收拢,避免误伤普通句号。
_RE_BROKEN_DECIMAL = re.compile(
r"(\d+)\s*[..。]\s*(\d+)(?=\s*(?:kgs?|公斤|千克|cbm|立方米|立方|m3|m³|方))",
re.I,
)
def _glue_broken_decimals(text: str) -> str:
"""
把「578. 5KG」「1. 03CBM」收成「578.5KG」「1.03CBM」。
中英文句号/全角点两侧有空格时,机读否则只命中小数段变成 5KG/03CBM。
副作用:无。不改主账。
"""
return _RE_BROKEN_DECIMAL.sub(r"\1.\2", text or "")
# 销售原话里的包装词:当前句出现则覆盖旧值,散货也要收下
_PACK_WORDS = ("纸箱", "木箱", "托盘", "卡板", "散货")
# 销售口中的报价日期。内部键仍是报价日期。货好时间、提货地址不是这里。
@@ -439,10 +459,12 @@ def harvest_oral_measures(text: str, facts: dict[str, str] | None = None) -> dic
当前句写了的覆盖旧值:散货替换托盘,9月16日写入报价日期。
补问复制清单的示例行不参与机读。
小数点两侧空格先收拢,再匹配单位。
副作用:无。不改主账。不猜港口。
"""
merged = dict(facts or {})
raw = _without_clarify_examples(text or "")
raw0 = _without_clarify_examples(text or "")
raw = _glue_broken_decimals(raw0)
hit = _RE_PIECES.search(raw)
if hit:
merged["件数"] = f"{hit.group(1)}{hit.group(2)}"
@@ -656,6 +678,27 @@ def looks_like_container_spec(text: str) -> bool:
return bool(_CONTAINER_SPEC.search(text or ""))
# 货量里除箱型外还有装满/吨等意图时,不能当「误写入的箱型」丢掉。
_CARGO_QTY_INTENT = re.compile(
r"装满|满柜|满载|吨|件|KG|KGS|CBM|立方|\d+\s*方|票|报价\s*\d|\d+\s*个价格",
re.I,
)
def is_container_only_cargo_qty(text: str) -> bool:
"""
货量是否整段只是箱型箱量写法。
「40HQ*1」「1x40HQ」应从货量剔除;「40HQ、20GP 装满,报价2个价格」是销售在答货物数量,保留。
"""
raw = (text or "").strip()
if not looks_like_container_spec(raw):
return False
if _CARGO_QTY_INTENT.search(raw):
return False
return True
def promote_land_vehicle_from_container(facts: dict[str, str] | None) -> dict[str, str]:
"""
陆运时把误落入「箱型箱量」的车型改写到「车型/数量」。
@@ -697,18 +740,21 @@ def project_sea_cargo_qty(facts: dict[str, str] | None) -> dict[str, str]:
海运货物数量投影。
抽字段常把「20件」放进件数,又把「40HQ*1」写进货量。
货量只要长得像箱型,就丢掉,改用件数里的数量;件数也是箱型则不搬。
箱型箱量空着时,把这串箱型补到箱型箱量。不限具体件数。
货量若整段只是箱型写法,就丢掉,改用件数里的数量;件数也是箱型则不搬。
「40HQ 装满」这类带货量意图的保留,不能再逼销售重填。
箱型箱量空着时,把纯箱型串补到箱型箱量。不限具体件数。
"""
merged = dict(facts or {})
qty = (merged.get("货量") or merged.get("货物数量") or "").strip()
pieces = (merged.get("件数") or "").strip()
if looks_like_container_spec(qty):
if is_container_only_cargo_qty(qty):
if not (merged.get("箱型箱量") or "").strip():
merged["箱型箱量"] = qty
merged.pop("货量", None)
merged.pop("货物数量", None)
qty = ""
elif looks_like_container_spec(qty) and _CARGO_QTY_INTENT.search(qty):
pass
if not qty and pieces and not looks_like_container_spec(pieces):
merged["货量"] = pieces
return merged