feat: 会话编排建材化+阿语号码转ASCII+12小时话术跨层同步

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-29 17:59:45 +08:00
parent 874308c099
commit 9caf3c4501
8 changed files with 88 additions and 46 deletions
+2 -2
View File
@@ -158,12 +158,12 @@ def test_asks_contact_detects_production_log_phrasings():
assert asks_contact("Drop yours too and our manager will send you the full quote!")
assert asks_contact("留个WhatsApp或者邮箱吧?")
assert asks_contact("你WhatsApp多少?")
assert asks_contact("你给我个联系方式,我让客户经理给你推几个车型。")
assert asks_contact("你给我个联系方式,我让客户经理给你推几个产品方案。")
def test_asks_contact_clean_phrasings_pass():
assert not asks_contact("Got your number, thanks!") # 确认,非问句
assert not asks_contact("Just tap the WhatsApp button at the top of this chat — our manager replies fast!")
assert not asks_contact("Just tap the WhatsApp button at the top of this chat — our manager will reply within 12 hours!")
assert not asks_contact("Which color do you prefer?")
assert not asks_contact("What's your budget range for this one?")
assert not asks_contact("Can you send me more details about the Tang?") # 泛请求无渠道词
+22
View File
@@ -342,3 +342,25 @@ def test_number_first_whatsapp_whitespace_variants():
# 两个接线位(scan + latest)都要走空白归一,任一崩都算回归
assert latest_stated_contacts(m("0712345678 is my whats app")) == {
"whatsapp": "0712345678"}
# ---- 阿语/波斯语数字归一化(中东买家): 存库与比对全链 ASCII ----
def test_arabic_digits_normalized():
# 阿语数字(中东买家)提取后应转 ASCII,CRM 可直接拨
confirmed, _ = split_validate({"whatsapp": "+٩٦٦٥٠١٢٣٤٥٦٧"})
assert confirmed["whatsapp"] == "+966501234567"
def test_arabic_digits_scan_latest_translated():
# 捕获源头即转 ASCII: 用户消息里的阿语号码,scan/latest 的落库形态
# 直接可拨;canon 比对也与 ASCII 形态同口径(不触发假性"改号"更正邮件)
msgs = [{"role": "user", "content": "whatsapp: ٩٦٦٥٠١٢٣٤٥٦٧"}]
assert scan_user_messages(msgs) == {"whatsapp": "966501234567"}
assert latest_stated_contacts(msgs) == {"whatsapp": "966501234567"}
def test_arabic_digits_canon_equals_ascii_form():
from app.services.contact_pipeline import canon
assert canon("+٩٦٦ ٥٠١ ٢٣٤ ٥٦٧") == canon("+966 501 234 567")
+5 -5
View File
@@ -1,7 +1,7 @@
"""Red-line: `context` injection must be CONDITIONAL + WHITELIST only.
- No `context` passed -> `raw_info` stays `{}` (widget WS path unchanged).
- `context` passed -> only `vehicle_name`/`vehicle_id` map to `app_*`.
- `context` passed -> only `product_name`/`product_id` map to `app_*`.
"""
from sqlalchemy import select
@@ -33,13 +33,13 @@ async def test_context_seeds_whitelist_only(async_db):
mgr = ConversationManager(db)
conv = await mgr.create_conversation(
context={
"vehicle_name": "Tesla Model Y",
"vehicle_id": "v123",
"product_name": "HDPE PE100 Pipe DN110",
"product_id": "p123",
"evil_key": "should_be_ignored",
}
)
raw = await _raw_info_for(async_db, conv.id)
assert raw == {
"app_vehicle_name": "Tesla Model Y",
"app_vehicle_id": "v123",
"app_product_name": "HDPE PE100 Pipe DN110",
"app_product_id": "p123",
}
+4 -4
View File
@@ -482,17 +482,17 @@ def test_reminder_boundary_covers_delivery_and_arrangement():
assert "职能边界" in ConversationManager._build_context_reminder(
{}, "how long does shipping take?", [])
assert "职能边界" in ConversationManager._build_context_reminder(
{}, "可以约试驾吗", [])
{}, "可以安排验厂吗", [])
assert "职能边界" in ConversationManager._build_context_reminder(
{}, "my budget is 20000, any recommendation?", [])
def test_reminder_no_boundary_on_normal_car_questions():
# 续航/配置/库存类正常问题不得误触发
# 规格/配置/库存类正常问题不得误触发
assert "职能边界" not in ConversationManager._build_context_reminder(
{}, "what is the range of the Tang?", [])
{}, "what is the pressure rating of the PE100 pipe?", [])
assert "职能边界" not in ConversationManager._build_context_reminder(
{}, "这款车续航多久", [])
{}, "这根管耐压多少", [])
def test_reminder_boundary_on_pressure_phrases():