2026-10-20 19:35:08 +08:00
|
|
|
|
"""site_import 骨架模板与 few-shot 语料守卫(Task 4)。"""
|
|
|
|
|
|
import json
|
|
|
|
|
|
import re
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
TPL_DIR = Path(__file__).resolve().parents[1] / "app" / "platform" / "site_import" / "template"
|
|
|
|
|
|
FS = Path(__file__).resolve().parents[1] / "app" / "platform" / "site_import" / "fewshot" / "fewshot_excerpts.md"
|
|
|
|
|
|
JIANCAI_SYSTEM = Path(__file__).resolve().parents[1] / "seed_profiles" / "jiancai" / "prompts" / "system.md"
|
|
|
|
|
|
|
|
|
|
|
|
# 行业专属词黑名单(jiancai 管道 / yotai 储能 / televics 炉辊三行业的专属词;
|
|
|
|
|
|
# WhatsApp、manager、quotation 等通用外贸词不在其列)
|
|
|
|
|
|
INDUSTRY_WORDS = [
|
|
|
|
|
|
"管道", "pipe", "HESUPIPE", "储能", "storage", "炉辊", "roller",
|
|
|
|
|
|
"TELEVIC", "YOTAI", "HDPE", "PVC", "PE100", "SDR", "DN110",
|
|
|
|
|
|
"给水", "燃气", "排污", "灌溉", "波纹管", "球墨铸铁",
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-11-02 22:54:30 +08:00
|
|
|
|
# 双要分叉豁免清单(2026-11-02,spec docs/superpowers/specs/
|
|
|
|
|
|
# 2026-11-02-jiancai-dual-contact-ask-design.md §3.4):jiancai system.md
|
|
|
|
|
|
# 已演化到"双渠道一次要齐"话术,生成器模板按 spec 决策**有意保持**"分步双要"
|
|
|
|
|
|
# 出厂默认——联系方式引导话术族的行级分叉是已归档的合规分叉,按行特征子串
|
|
|
|
|
|
# 豁免不计 missing;其余行仍受 ≤8 阈值守护(防未归档漂移)。
|
|
|
|
|
|
DUAL_ASK_DIVERGED = (
|
|
|
|
|
|
'互留句 "Drop yours too..."', # 模板句族行(L7 同构)
|
|
|
|
|
|
"I'll connect you with our manager to arrange that — drop your WhatsApp", # 转接句
|
|
|
|
|
|
"Tell me your WhatsApp and our manager", # 预算后引导句
|
|
|
|
|
|
"drop your WhatsApp and our manager will reply within 12 hours", # 磨价应对句
|
|
|
|
|
|
"记录里确有号码", # 已留资确认句
|
|
|
|
|
|
"4. 拿到联系方式后", # 对话流程尾括注
|
|
|
|
|
|
"What's your WhatsApp number with the country code", # en 池①
|
|
|
|
|
|
"who handles [country] market", # en 池②
|
|
|
|
|
|
"Drop your WhatsApp or email?", # en 池③
|
|
|
|
|
|
"Want me to get you a full quotation?", # en 池④
|
|
|
|
|
|
"你WhatsApp多少?带上国家代码", # zh 池①
|
|
|
|
|
|
"我让负责[国家]市场的同事加你", # zh 池②
|
|
|
|
|
|
"留个WhatsApp或者邮箱吧", # zh 池③
|
|
|
|
|
|
"你给我个联系方式,我让客户经理", # zh 池④
|
|
|
|
|
|
"**分步双要,一次只问一个**", # 双渠道原则行
|
|
|
|
|
|
'"Drop your WhatsApp and our manager', # App 反向留资句
|
|
|
|
|
|
"【互留】", # 互留句
|
|
|
|
|
|
'提醒说"已提供"时', # 系统提醒配合行
|
|
|
|
|
|
"拿到后说一句", # 注意节尾括注
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-10-20 19:35:08 +08:00
|
|
|
|
|
|
|
|
|
|
class TestTemplatePurity:
|
|
|
|
|
|
def test_骨架无行业残留(self):
|
|
|
|
|
|
for fname in ("system", "extractor", "evaluator"):
|
|
|
|
|
|
t = (TPL_DIR / f"{fname}_template.md").read_text(encoding="utf-8")
|
|
|
|
|
|
low = t.lower()
|
|
|
|
|
|
for w in INDUSTRY_WORDS:
|
|
|
|
|
|
assert w.lower() not in low, f"{fname}_template.md 行业残留: {w}"
|
|
|
|
|
|
|
|
|
|
|
|
def test_占位符与清单一致(self):
|
|
|
|
|
|
ph = json.loads((TPL_DIR / "placeholders.json").read_text(encoding="utf-8"))
|
|
|
|
|
|
assert set(ph) == {"system", "extractor", "evaluator"}
|
|
|
|
|
|
for fname, keys in ph.items():
|
|
|
|
|
|
t = (TPL_DIR / f"{fname}_template.md").read_text(encoding="utf-8")
|
|
|
|
|
|
for k in keys:
|
|
|
|
|
|
assert "{{" + k + "}}" in t, f"{fname} 缺占位符 {k}"
|
|
|
|
|
|
# 反向:模板中每个 {{xxx}} 都在清单
|
|
|
|
|
|
for fname, keys in ph.items():
|
|
|
|
|
|
t = (TPL_DIR / f"{fname}_template.md").read_text(encoding="utf-8")
|
|
|
|
|
|
found = set(re.findall(r"\{\{([^}]+)\}\}", t))
|
|
|
|
|
|
assert found == set(keys), f"{fname} 占位符不对称: {found ^ set(keys)}"
|
|
|
|
|
|
|
|
|
|
|
|
def test_红线段锚点齐全(self):
|
|
|
|
|
|
t = (TPL_DIR / "system_template.md").read_text(encoding="utf-8")
|
|
|
|
|
|
for anchor in ["语言规则(最高优先级)", "职能边界", "红线(任何情况下不可违反)",
|
|
|
|
|
|
"不能出现任何价格数字", "施压", "禁止说的话", "联系方式引导策略",
|
|
|
|
|
|
"12 hours", "国家代码"]:
|
|
|
|
|
|
assert anchor in t, f"缺锚点: {anchor}"
|
|
|
|
|
|
|
|
|
|
|
|
def test_固定段行级来源(self):
|
2026-11-02 22:54:30 +08:00
|
|
|
|
"""非占位行应逐字存在于 jiancai 原文(允许空行/标题差异 ±5% 行)。
|
|
|
|
|
|
|
|
|
|
|
|
双要改版后联系方式话术族的分叉按 DUAL_ASK_DIVERGED 豁免(见其注)。
|
|
|
|
|
|
"""
|
2026-10-20 19:35:08 +08:00
|
|
|
|
j = JIANCAI_SYSTEM.read_text(encoding="utf-8").splitlines()
|
|
|
|
|
|
jset = set(l.strip() for l in j)
|
|
|
|
|
|
t = (TPL_DIR / "system_template.md").read_text(encoding="utf-8").splitlines()
|
|
|
|
|
|
missing = [l.strip() for l in t
|
|
|
|
|
|
if l.strip() and "{{" not in l and "#" not in l[:2]
|
2026-11-02 22:54:30 +08:00
|
|
|
|
and l.strip() not in jset
|
|
|
|
|
|
and not any(m in l for m in DUAL_ASK_DIVERGED)]
|
2026-10-20 19:35:08 +08:00
|
|
|
|
# 允许少量结构性改写(拆段标题等)——阈值 ≤8 行,且打印出来人工可核
|
|
|
|
|
|
assert len(missing) <= 8, f"来源外行过多: {missing}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestFewshot:
|
|
|
|
|
|
def test_尺寸上限(self):
|
|
|
|
|
|
assert len(FS.read_bytes()) <= 18000
|
|
|
|
|
|
|
|
|
|
|
|
def test_两租户都在(self):
|
|
|
|
|
|
s = FS.read_text(encoding="utf-8")
|
|
|
|
|
|
assert "yotaienergys" in s and "televicsic" in s
|
|
|
|
|
|
|
|
|
|
|
|
def test_用途段落齐(self):
|
|
|
|
|
|
s = FS.read_text(encoding="utf-8")
|
|
|
|
|
|
for sec in ["身份", "追问", "好差", "事实卡", "提取器", "评估器"]:
|
|
|
|
|
|
assert sec in s, f"fewshot 缺用途段: {sec}"
|