落地陆运群内协同:按填写样例拉群并只列本单协同项,进群把私聊已抽到的字段带上。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,12 +23,31 @@ _MODE_WORDS = (
|
||||
_LABEL_LINE = re.compile(
|
||||
r"^(起运地|起运港|目的地|目的港|品名|件数|毛重|体积|重量|重量\(KG\)|体积\(CBM\)|"
|
||||
r"包装方式|包装类型|货量|货物数量|整柜或拼柜|箱型箱量|报价日期|车型/数量|车型数量|"
|
||||
r"贸易条款|货好时间|商品海关编码|海关编码|HS编码|HS|是否含油|是否含电|是否含磁)\s*[::]\s*(.+)$"
|
||||
r"贸易条款|货好时间|商品海关编码|海关编码|HS编码|HS|是否含油|是否含电|是否含磁|"
|
||||
r"客户名称|货值|是否为危险品)\s*[::]\s*(.+)$"
|
||||
)
|
||||
_LABEL_INLINE = re.compile(
|
||||
r"(起运地|起运港|目的地|目的港|品名|件数|毛重|体积|重量|重量\(KG\)|体积\(CBM\)|"
|
||||
r"包装方式|包装类型|货量|货物数量|整柜或拼柜|箱型箱量|报价日期|车型/数量|车型数量|"
|
||||
r"贸易条款|货好时间|商品海关编码|海关编码|HS编码|HS|是否含油|是否含电|是否含磁)\s*[::]\s*(\S+)"
|
||||
r"贸易条款|货好时间|商品海关编码|海关编码|HS编码|HS|是否含油|是否含电|是否含磁|"
|
||||
r"客户名称|货值|是否为危险品)\s*[::]\s*(\S+)"
|
||||
)
|
||||
# 群里协同标签,冒号可省略:「客户名称张三」「货值 10万」。长词优先。
|
||||
_COLLAB_LABELS = (
|
||||
"是否为危险品",
|
||||
"商品海关编码",
|
||||
"车型/数量",
|
||||
"包装方式",
|
||||
"客户名称",
|
||||
"海关编码",
|
||||
"贸易条款",
|
||||
"货好时间",
|
||||
"是否含油",
|
||||
"是否含电",
|
||||
"是否含磁",
|
||||
"HS编码",
|
||||
"货值",
|
||||
"HS",
|
||||
)
|
||||
|
||||
|
||||
@@ -64,6 +83,23 @@ def parse_labeled_facts(text: str) -> dict[str, str]:
|
||||
return facts
|
||||
|
||||
|
||||
def _merge_spoken_collab(text: str, facts: dict[str, str]) -> dict[str, str]:
|
||||
"""
|
||||
私聊已经说到的协同项写进事实,进群才能预填。
|
||||
|
||||
不覆盖已有询价键。不拿去补问、不挡查价。
|
||||
"""
|
||||
from agent.schema.field_validate import harvest_oral_collab, normalize_facts
|
||||
|
||||
merged = dict(facts or {})
|
||||
extra = dict(harvest_oral_collab(text))
|
||||
extra.update(normalize_facts(harvest_collab_labels(text)))
|
||||
for key, val in extra.items():
|
||||
if val and not str(merged.get(key) or "").strip():
|
||||
merged[key] = val
|
||||
return merged
|
||||
|
||||
|
||||
def extract_inquiry_snapshot(
|
||||
text: str,
|
||||
*,
|
||||
@@ -80,11 +116,12 @@ def extract_inquiry_snapshot(
|
||||
标签行覆盖 B 的同名字段。B 未出现的字段保持空,不本地猜港口。
|
||||
件数/毛重/体积若原话已带 TMS 单位(件/KGS/CBM),空字段可机读补上。
|
||||
"""
|
||||
from agent.schema.field_validate import harvest_oral_measures, normalize_facts
|
||||
from agent.schema.field_validate import harvest_oral_collab, harvest_oral_measures, normalize_facts
|
||||
|
||||
if injected_facts is not None:
|
||||
mode = (injected_mode or detect_transport_mode(text) or "").upper()
|
||||
facts = harvest_oral_measures(text, normalize_facts(dict(injected_facts)))
|
||||
facts = _merge_spoken_collab(text, facts)
|
||||
subtype = str(facts.get("运输类型") or "").strip()
|
||||
return {
|
||||
"business_line": mode,
|
||||
@@ -109,6 +146,7 @@ def extract_inquiry_snapshot(
|
||||
mode = mode or str(b_payload.get("business_line") or "")
|
||||
source = "deepseek_b"
|
||||
facts = harvest_oral_measures(text, normalize_facts(facts))
|
||||
facts = _merge_spoken_collab(text, facts)
|
||||
land_subtype = str((b_payload or {}).get("land_subtype") or facts.get("运输类型") or "").strip()
|
||||
if land_subtype and not str(facts.get("运输类型") or "").strip():
|
||||
facts["运输类型"] = land_subtype
|
||||
@@ -123,37 +161,90 @@ def extract_inquiry_snapshot(
|
||||
}
|
||||
|
||||
|
||||
def harvest_collab_labels(text: str) -> dict[str, str]:
|
||||
"""
|
||||
群里协同标签:有无冒号都收。
|
||||
|
||||
「客户名称张三 包装方式编织袋 货值 10万」切成三项。
|
||||
只说「纸箱」没有标签则不收。值取到下一个标签之前。
|
||||
"""
|
||||
raw = text or ""
|
||||
hits: list[tuple[int, str]] = []
|
||||
for label in _COLLAB_LABELS:
|
||||
start = 0
|
||||
while True:
|
||||
pos = raw.find(label, start)
|
||||
if pos < 0:
|
||||
break
|
||||
hits.append((pos, label))
|
||||
start = pos + len(label)
|
||||
if not hits:
|
||||
return {}
|
||||
hits.sort()
|
||||
# 同一起点只留最长标签,避免 HS 抢 HS编码。
|
||||
kept: list[tuple[int, str]] = []
|
||||
for pos, label in hits:
|
||||
if kept and kept[-1][0] == pos and len(label) <= len(kept[-1][1]):
|
||||
continue
|
||||
if kept and kept[-1][0] == pos:
|
||||
kept[-1] = (pos, label)
|
||||
continue
|
||||
kept.append((pos, label))
|
||||
out: dict[str, str] = {}
|
||||
for i, (pos, label) in enumerate(kept):
|
||||
begin = pos + len(label)
|
||||
end = kept[i + 1][0] if i + 1 < len(kept) else len(raw)
|
||||
chunk = raw[begin:end]
|
||||
chunk = re.sub(r"^[\s::,,、]+", "", chunk)
|
||||
chunk = re.sub(r"[\s,,、]+$", "", chunk)
|
||||
if chunk:
|
||||
out[label] = chunk
|
||||
return out
|
||||
|
||||
|
||||
def extract_collab_fields(
|
||||
text: str,
|
||||
*,
|
||||
allow_b: bool = False,
|
||||
injected: Optional[dict[str, str]] = None,
|
||||
allowed_keys: Optional[tuple[str, ...]] = None,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
群里协同字段:标签「键:值」+ 口语含油/含电/含磁、货好时间。
|
||||
群里协同字段:标签(冒号可省)+ 口语含油/含电/含磁/危险品、货好时间。
|
||||
|
||||
闲聊、报价金额不进这里。商品海关编码 / HS 折成 HS编码。
|
||||
allow_b 保留给调用方;协同六项不走询价七项的 B 合同。
|
||||
allowed_keys 传入则只返回本单该有的项。不传则海运六项 ∪ 陆运协同键,给意图分类。
|
||||
闲聊、只说「纸箱」不进这里。商品海关编码 / HS 折成 HS编码。
|
||||
"""
|
||||
from agent.schema.field_validate import (
|
||||
SEA_COLLAB_FIELDS,
|
||||
ALL_COLLAB_KEYS,
|
||||
harvest_oral_collab,
|
||||
normalize_facts,
|
||||
)
|
||||
|
||||
del allow_b
|
||||
keys = tuple(allowed_keys) if allowed_keys is not None else ALL_COLLAB_KEYS
|
||||
if injected:
|
||||
return {
|
||||
k: str(v).strip()
|
||||
for k, v in normalize_facts(dict(injected)).items()
|
||||
if k in SEA_COLLAB_FIELDS and str(v or "").strip()
|
||||
if k in keys and str(v or "").strip()
|
||||
}
|
||||
labeled = normalize_facts(parse_labeled_facts(text))
|
||||
labeled.update(normalize_facts(harvest_collab_labels(text)))
|
||||
oral = harvest_oral_collab(text)
|
||||
if "是否为危险品" in oral:
|
||||
if allowed_keys is not None and "是否为危险品" not in keys:
|
||||
oral.pop("是否为危险品", None)
|
||||
elif allowed_keys is None:
|
||||
raw = text or ""
|
||||
if "普货" in raw and not any(
|
||||
w in raw for w in ("危险", "非危", "有危", "含危", "是否为危险品")
|
||||
):
|
||||
oral.pop("是否为危险品", None)
|
||||
merged = dict(oral)
|
||||
merged.update({k: v for k, v in labeled.items() if v})
|
||||
out: dict[str, str] = {}
|
||||
for key in SEA_COLLAB_FIELDS:
|
||||
for key in keys:
|
||||
val = (merged.get(key) or "").strip()
|
||||
if val:
|
||||
out[key] = val
|
||||
|
||||
Reference in New Issue
Block a user