私聊建单前拦住枪支弹药爆炸物和毒品,避免真家伙进入报价。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
私聊建单前的违禁品拦截。
|
||||
|
||||
本文件职责:用固定词表判断当前这句话、尚未改口的原话,以及品名、起运地、备注
|
||||
是否涉及真家伙(枪支、弹药、爆炸物、毒品)。命中则不能建单。
|
||||
群聊、已经建成的工单不在这里判断,由调用方避开。
|
||||
调用:Worker 里的私聊文字流程,在出补问或建单之前。只做字符串匹配,不调模型。
|
||||
禁止:把水枪、玩具枪、仿真枪、气枪、烟花、礼炮、鞭炮算进去;禁止拦化学品、电池、油品。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
# 销售私聊里只回这一句。不要再追问缺项,也不要带工单号。
|
||||
BLOCK_REPLY = "涉及危险品,无法发起报价。"
|
||||
|
||||
# 先圈出放行说法。词表里的「枪支」若落在这些说法内部,不算命中。
|
||||
_ALLOW = (
|
||||
"玩具枪",
|
||||
"水枪",
|
||||
"仿真枪",
|
||||
"气枪",
|
||||
"烟花",
|
||||
"礼炮",
|
||||
"鞭炮",
|
||||
"炮仗",
|
||||
)
|
||||
|
||||
# 真家伙。不用单字「枪」「炮」「弹」,避免误伤水枪和礼炮。
|
||||
_BLOCK = (
|
||||
"汽油弹",
|
||||
"燃烧弹",
|
||||
"原子弹",
|
||||
"手榴弹",
|
||||
"导火索",
|
||||
"甲基苯丙胺",
|
||||
"氯胺酮",
|
||||
"高射炮",
|
||||
"榴弹炮",
|
||||
"冲锋枪",
|
||||
"狙击枪",
|
||||
"霰弹枪",
|
||||
"火药枪",
|
||||
"摇头丸",
|
||||
"海洛因",
|
||||
"可卡因",
|
||||
"炸弹",
|
||||
"炸药",
|
||||
"爆破",
|
||||
"雷管",
|
||||
"手雷",
|
||||
"地雷",
|
||||
"导弹",
|
||||
"核弹",
|
||||
"枪支",
|
||||
"枪械",
|
||||
"手枪",
|
||||
"步枪",
|
||||
"机枪",
|
||||
"猎枪",
|
||||
"子弹",
|
||||
"弹药",
|
||||
"弹头",
|
||||
"大炮",
|
||||
"火炮",
|
||||
"毒品",
|
||||
"冰毒",
|
||||
"大麻",
|
||||
"鸦片",
|
||||
"吗啡",
|
||||
"K粉",
|
||||
)
|
||||
|
||||
# 改口动词。多段原文里的「品名:衣服」不能走这里,否则会抄到每一段。
|
||||
_FIELD_EDIT = re.compile(
|
||||
r"(货物品名|品名|起运地|起运港|始发站|备注)\s*(?:改为|改成|是|为)\s*(\S+)"
|
||||
)
|
||||
# 单票里销售按清单重写「品名:服装」。多段「段1 / 段2」整单不靠这个改字段。
|
||||
_LABEL_EDIT = re.compile(
|
||||
r"(货物品名|品名|起运地|起运港|始发站|备注)\s*[::]\s*(\S+)"
|
||||
)
|
||||
_MULTI_SHEET = re.compile(r"段\s*\d|第\s*[一二三四五六七八九\d]\s*段")
|
||||
|
||||
# 销售侧说法写回内部键。起运地在主账里仍是起运港。
|
||||
_WRITE_KEYS = {
|
||||
"品名": ("品名", "货物品名"),
|
||||
"货物品名": ("品名", "货物品名"),
|
||||
"起运地": ("起运港", "起运地", "始发站"),
|
||||
"起运港": ("起运港", "起运地", "始发站"),
|
||||
"始发站": ("起运港", "起运地", "始发站"),
|
||||
"备注": ("备注",),
|
||||
}
|
||||
|
||||
# 建单前要看的当前字段。没写进这几项、但原话里出现过的,靠 sensitive_hold 记住。
|
||||
_SCAN_KEYS = ("品名", "货物品名", "起运港", "起运地", "始发站", "备注")
|
||||
|
||||
|
||||
def contains_blocked(text: str) -> bool:
|
||||
"""
|
||||
这句话里有没有要拦的词。
|
||||
|
||||
放行说法内部的字不算。例如「玩具枪」放行,「玩具枪支」仍因「枪支」拦下。
|
||||
不做英文、拼音、谐音。纯字符串查找,可在 Worker 线程同步调用。
|
||||
"""
|
||||
raw = text or ""
|
||||
if not raw:
|
||||
return False
|
||||
allow_spans: list[tuple[int, int]] = []
|
||||
for phrase in _ALLOW:
|
||||
start = 0
|
||||
while True:
|
||||
found = raw.find(phrase, start)
|
||||
if found < 0:
|
||||
break
|
||||
allow_spans.append((found, found + len(phrase)))
|
||||
start = found + len(phrase)
|
||||
|
||||
def _inside(begin: int, end: int) -> bool:
|
||||
return any(left <= begin and end <= right for left, right in allow_spans)
|
||||
|
||||
for word in _BLOCK:
|
||||
start = 0
|
||||
while True:
|
||||
found = raw.find(word, start)
|
||||
if found < 0:
|
||||
break
|
||||
if not _inside(found, found + len(word)):
|
||||
return True
|
||||
start = found + len(word)
|
||||
return False
|
||||
|
||||
|
||||
def apply_field_edits(text: str, facts: dict[str, Any]) -> dict[str, str]:
|
||||
"""
|
||||
把当前这句话里的「品名改为…」「起运地改为…」「备注改为…」写进字段。
|
||||
|
||||
只改这一句写明的项。返回实际写下的键值,方便多段把品名写回每一段。
|
||||
会改传入的 facts。值末尾的标点去掉,避免「服装,」留在品名里。
|
||||
"""
|
||||
written: dict[str, str] = {}
|
||||
pairs = list(_FIELD_EDIT.findall(text or ""))
|
||||
if not _MULTI_SHEET.search(text or ""):
|
||||
pairs.extend(_LABEL_EDIT.findall(text or ""))
|
||||
for label, raw_value in pairs:
|
||||
value = str(raw_value or "").strip(",,。;;、 ")
|
||||
if not value:
|
||||
continue
|
||||
for key in _WRITE_KEYS.get(label, (label,)):
|
||||
facts[key] = value
|
||||
written[key] = value
|
||||
return written
|
||||
|
||||
|
||||
def is_cargo_rename(text: str) -> bool:
|
||||
"""当前句有没有把品名重新说成一个新值。没说品名的补重量不算改口。"""
|
||||
body = text or ""
|
||||
labels = [label for label, _value in _FIELD_EDIT.findall(body)]
|
||||
if not _MULTI_SHEET.search(body):
|
||||
labels.extend(label for label, _value in _LABEL_EDIT.findall(body))
|
||||
return any(label in {"品名", "货物品名"} for label in labels)
|
||||
|
||||
|
||||
def judge(*, text: str, facts: dict[str, Any], hold: str) -> tuple[bool, str]:
|
||||
"""
|
||||
决定这一轮私聊能不能继续往建单走。
|
||||
|
||||
返回 (是否拦截, 新的未改口原话, 本句写下的字段)。
|
||||
当前句里仍有违禁词:拦住,并把这句记下来。
|
||||
当前句把品名说成普通货、且这句里没有违禁词:以前没落进字段的那句作废。
|
||||
只补重量、件数:旧原话继续有效。
|
||||
品名改干净之后,起运地或备注里还留着违禁词,仍然拦截。
|
||||
会就地更新 facts 里本句写明的品名、起运地、备注。
|
||||
第三个返回值是本句写下的键,多段用来写回每一段,避免把别的字段盖掉。
|
||||
"""
|
||||
edits = apply_field_edits(text, facts)
|
||||
message_hit = contains_blocked(text)
|
||||
if message_hit:
|
||||
new_hold = (text or "").strip() or (hold or "")
|
||||
elif is_cargo_rename(text):
|
||||
new_hold = ""
|
||||
else:
|
||||
new_hold = hold or ""
|
||||
field_text = "\n".join(str((facts or {}).get(key) or "") for key in _SCAN_KEYS)
|
||||
blocked = message_hit or contains_blocked(new_hold) or contains_blocked(field_text)
|
||||
return blocked, new_hold, edits
|
||||
Reference in New Issue
Block a user