私聊改口成普通货后不再被上一句违禁词拦住。

销售说运送衣服、改成衣服时,按新品名继续询价。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-14 20:30:53 +08:00
co-authored by Cursor
parent 617c5e2619
commit a0c705683f
2 changed files with 55 additions and 5 deletions
+41 -5
View File
@@ -82,6 +82,13 @@ _LABEL_EDIT = re.compile(
r"(货物品名|品名|起运地|起运港|始发站|备注)\s*[::]\s*(\S+)"
)
_MULTI_SHEET = re.compile(r"段\s*\d|第\s*[一二三四五六七八九\d]\s*段")
# 口语改口:运送衣服、改成衣服、换成衣服、炸弹换衣服。不认「换一下」「换个港口」。
_SPOKEN_CARGO = re.compile(
r"(?:运送|托运|发运|改成|改为|换成|换为)\s*([^\s,,。;;、请]{1,16})"
r"|换(?!成|为|个|一下)([^\s,,。;;、请]{1,16})"
)
_CARGO_TAIL = re.compile(r"(请报价|报价|一下|吧|了)+$")
_CARGO_SKIP = frozenset({"一下", "个", "报价", "价", "询价", "港口", "地方"})
# 销售侧说法写回内部键。起运地在主账里仍是起运港。
_WRITE_KEYS = {
@@ -132,6 +139,24 @@ def contains_blocked(text: str) -> bool:
return False
def spoken_cargo(text: str) -> str:
"""
从这一句里取出销售刚说的品名。
「运送衣服」「改成衣服」「换成衣服」「炸弹换衣服」都算。
取最后一个说法,所以「炸弹换衣服」得到衣服。
说出来的还是违禁词,或者只是「换一下」,就当没改口。
"""
found = ""
for match in _SPOKEN_CARGO.finditer(text or ""):
raw = match.group(1) or match.group(2) or ""
value = _CARGO_TAIL.sub("", raw).strip(",,。;;、 ")
if not value or value in _CARGO_SKIP or contains_blocked(value):
continue
found = value
return found
def apply_field_edits(text: str, facts: dict[str, Any]) -> dict[str, str]:
"""
把当前这句话里的「品名改为…」「起运地改为…」「备注改为…」写进字段。
@@ -150,11 +175,18 @@ def apply_field_edits(text: str, facts: dict[str, Any]) -> dict[str, str]:
for key in _WRITE_KEYS.get(label, (label,)):
facts[key] = value
written[key] = value
cargo = spoken_cargo(text)
if cargo:
for key in ("品名", "货物品名"):
facts[key] = cargo
written[key] = cargo
return written
def is_cargo_rename(text: str) -> bool:
"""当前句有没有把品名重新说成一个新值。没说品名的补重量不算改口。"""
if spoken_cargo(text):
return True
body = text or ""
labels = [label for label, _value in _FIELD_EDIT.findall(body)]
if not _MULTI_SHEET.search(body):
@@ -167,19 +199,23 @@ def judge(*, text: str, facts: dict[str, Any], hold: str) -> tuple[bool, str]:
决定这一轮私聊能不能继续往建单走。
返回 (是否拦截, 新的未改口原话, 本句写下的字段)。
当前句里仍有违禁词:拦住,并把这句记下来。
当前句把品名说成普通货、且这句里没有违禁词:以前没落进字段的那句作废。
当前句仍只是在说违禁词:拦住,并把这句记下来。
当前句已经把品名说成普通货(运送衣服、改成衣服、换成衣服):前面的违禁原话作废。
同一句里先提到旧词再改口,按改口后的品名放行。
只补重量、件数:旧原话继续有效。
品名改干净之后,起运地或备注里还留着违禁词,仍然拦截。
会就地更新 facts 里本句写明的品名、起运地、备注。
第三个返回值是本句写下的键,多段用来写回每一段,避免把别的字段盖掉。
"""
edits = apply_field_edits(text, facts)
replaced = bool(spoken_cargo(text)) or is_cargo_rename(text)
message_hit = contains_blocked(text)
if message_hit:
new_hold = (text or "").strip() or (hold or "")
elif is_cargo_rename(text):
if replaced and not contains_blocked(str(facts.get("品名") or "")):
# 已经说出新的普通货品名。旧词留在这句里只是在改口,不再记住。
new_hold = ""
message_hit = False
elif message_hit:
new_hold = (text or "").strip() or (hold or "")
else:
new_hold = hold or ""
field_text = "\n".join(str((facts or {}).get(key) or "") for key in _SCAN_KEYS)
@@ -157,6 +157,20 @@ class SeaPrivateFlowTests(unittest.TestCase):
joined = "\n".join(self.replies)
self.assertNotIn(BLOCK_REPLY, joined)
def test_oral_restatement_clears_previous_bomb(self) -> None:
"""销售不说「品名改为」,直接重说运送衣服,或说改成衣服。"""
hold = "10月30日,从广州去深圳,运送炸弹,请报价"
facts = {"品名": "炸弹", "起运港": "广州"}
for text in (
"10月30日,从广州去深圳,运送衣服,请报价",
"炸弹错了,给我改成衣服",
"炸弹换衣服",
):
blocked, new_hold, edits = judge(text=text, facts=dict(facts), hold=hold)
self.assertFalse(blocked, text)
self.assertEqual(new_hold, "", text)
self.assertEqual(edits.get("品名"), "衣服", text)
def test_weight_alone_does_not_clear(self) -> None:
facts = dict(COMPLETE_SEA)
facts["品名"] = "弹药"