# -*- coding: utf-8 -*- """手册与代码的一致性检查(客服话术的可信度靠它)。 ## 为什么要查这个 手册(`docs/10-用户手册与客服话术.md`)的价值**完全建立在 "手册里那句话 == 用户屏幕上那句话"**上 —— 客服照着它回答, 对不上就会开始自己编,那比没有手册更糟。 而**手册是文档、文案在代码里**:改一次代码文案,手册就静默失准, 且没有任何东西会红。这与"首页在说一件不真的事"(F-82)是同一类问题。 所以这里把它变成机械检查,与 `check-api-parity.py`(契约↔路由)、 `check-entity-columns.py`(实体↔DDL)并列 —— 都是"文档与代码是否还对得上"。 ## 查两件事 1. 手册里引用的每个错误码(反引号包着的 5 位数)在 `ErrorCode.java` 里**确实存在**; 2. 手册里"客服会照着念"的那些提示文案在代码里**逐字存在**。 ⚠️ 它**不检查**手册有没有漏掉新错误码 —— 那要靠人。 它拦的是"手册写了代码里没有的东西"(这会让客服查到一半卡住)。 跑法:python scripts/check-manual.py """ import io import re import sys sys.stdout.reconfigure(encoding="utf-8") manual = io.open('docs/10-用户手册与客服话术.md', encoding='utf-8').read() codes_src = io.open('backend/xt-common/src/main/java/cn/tianbaotong/fill/common/api/' 'ErrorCode.java', encoding='utf-8').read() # ① 码:三个捕获组 → 自己建索引 rows = re.findall(r'([A-Z_]+)\((\d{4,5}),\s*"([^"]*)"\)', codes_src) declared_by_code = {code: (name, msg) for name, code, msg in rows} print("ErrorCode 里声明了 %d 个码" % len(declared_by_code)) used = sorted(set(re.findall(r'`(\d{5})`', manual))) print("手册引用了 %d 个码" % len(used)) bad = [] for c in used: if c in declared_by_code: name, msg = declared_by_code[c] print(" ✓ %s %-24s %s" % (c, name, msg[:44])) else: bad.append(c) print(" ✗ %s **ErrorCode 里没有这个码**" % c) # ② 关键文案 key_phrases = [ "没有找到你的信息,请核对后重试", "操作过于频繁,请稍后再试", "该信息已被登记", "你已在名单中,请返回重新核验", "你已提交过申请,请耐心等待", "这份材料可能不是你的,请重新上传", "已进入审核,需先撤回才能删材料", "已通过终审,不可撤回", "任务已截止、已归档或已暂停", "驳回次数已达上限", "存在不合格文件,禁止导出", "你的材料被退回了", "导出包已过期", "这个学生还没扫码核对身份", ] src_all = "" import glob for p in glob.glob('backend/**/*.java', recursive=True): try: src_all += io.open(p, encoding='utf-8').read() except Exception: pass print("\n核对手册里引用的提示文案(%d 句):" % len(key_phrases)) missing = [] for ph in key_phrases: if ph in src_all: print(" ✓ %s" % ph) else: missing.append(ph) print(" ✗ %s —— **代码里找不到这句**" % ph) print("\n结果:错误的码 %d 个,找不到的文案 %d 句" % (len(bad), len(missing))) sys.exit(1 if (bad or missing) else 0)