本文引用的文件
generate.pyrun_tests.pymanifest.json本仓库的测试体系包含三层内容:
docs/testing/fixtures/ai-llm-match-20261026 下的 Python 测试夹具,使用 generate.py 生成生产风格样本,用 run_tests.py 批量执行,并用 manifest.json 管理测试用例清单。文档 docs/testing/ai-template-match-test-cases.md 是该测试套件的核心说明;ai-llm-template-match-bug-report-20261026.md 记录了本轮发现的缺陷、根因分析和修复状态。
从仓库结构看,测试相关内容主要分布在以下位置:
| 路径 | 作用 |
|---|---|
docs/testing/ai-template-match-test-cases.md |
AI 模板匹配严格测试用例文档,定义 A/E 五类测试范围 |
docs/testing/ai-llm-template-match-bug-report-20261026.md |
LLM 模板匹配问题报告、复现步骤、修复记录 |
docs/testing/fixtures/ai-llm-match-20261026/ |
LLM 模板匹配测试夹具根目录 |
docs/testing/fixtures/ai-llm-match-20261026/bills/ |
银行流水相关测试样本 |
docs/testing/fixtures/ai-llm-match-20261026/calls/ |
通信话单相关测试样本 |
docs/testing/fixtures/ai-llm-match-20261026/full-template/ |
table_info 全量模板的原表头样本 |
docs/testing/fixtures/ai-llm-match-20261026/full-template-variants/ |
table_info 全量模板变体样本 |
docs/testing/fixtures/ai-llm-match-20261026/edge/ |
边界场景样本 |
docs/testing/fixtures/ai-llm-match-20261026/retest/ |
回归重测样本 |
docs/testing/fixtures/ai-llm-match-20261026/results/ |
批量执行产出的 JSON 结果 |
ai-electron/frontend/eslint.config.mjs |
前端 ESLint 配置 |
ai-electron/frontend/stylelint.config.mjs |
前端 Stylelint 配置 |
ai-electron/vitest.config.mts |
Electron 主进程 Vitest 配置 |
ai-electron/frontend/package.json |
前端脚本入口,包含 lint 命令 |
graph TB
Docs["测试文档<br/>ai-template-match-test-cases.md"]
BugReport["问题报告<br/>ai-llm-template-match-bug-report-20261026.md"]
Fixtures["测试夹具目录<br/>fixtures/ai-llm-match-20261026"]
Generate["generate.py<br/>生成测试数据"]
Runner["run_tests.py<br/>批量执行与结果收集"]
Manifest["manifest.json<br/>用例清单"]
Bills["bills/<br/>银行流水样本"]
Calls["calls/<br/>通信话单样本"]
FullTpl["full-template/<br/>全量模板原表头"]
Variants["full-template-variants/<br/>全量模板变体"]
Edge["edge/<br/>边界场景"]
Retest["retest/<br/>回归重测"]
Results["results/<br/>执行结果 JSON"]
Docs --> Fixtures
BugReport --> Fixtures
Generate --> Fixtures
Manifest --> Runner
Fixtures --> Runner
Bills --> Fixtures
Calls --> Fixtures
FullTpl --> Fixtures
Variants --> Fixtures
Edge --> Fixtures
Retest --> Fixtures
Runner --> Results
图示来源
章节来源
AI 模板匹配测试按确定性程度和链路深度分为五层:
| 层级 | 名称 | 被测内容 | 执行方式 | 特点 |
|---|---|---|---|---|
| A | 离线匹配规则 | 指纹、归一化、同义词、模糊词、打分组件 | JUnit | 确定性高,适合回归 |
| B | 绑定与计划 | filterBinds 域解析、改写、歧义、列计划 | JUnit | 验证字段映射逻辑 |
| C | 端到端真链路 | 上传 → 智能清洗 → job/批次/数据/治理树 | HTTP API + dev 数据库核验 | 覆盖完整业务流 |
| D | 异常容错 | 空表头、损坏文件、去重、跨案件 | 端到端 | 验证鲁棒性 |
| E | 全量模板覆盖 | table_info 中全部模板逐一同类型文件 | 端到端分批 | 覆盖既有匹配器与 AI 兜底 |
测试锚定三个行业场景:
| 场景 | 模板 | 关键列 | 出口 |
|---|---|---|---|
| 车辆登记 | AI 模板 2111790826933268480 |
号牌号码、机动车所有人、身份证明号码 | B |
| 银行交易流水 | 既有 table_info#2 trans_record |
交易金额、借贷标志、查询卡号 | A |
| 通信记录 | 既有 table_info#1 call_record |
本机号码、对方号码、呼叫开始时间 | A |
其中,“既有直中”指上传阶段被既有匹配器命中后直接走既有清洗,不再进入 AI 兜底链路。这是设计行为,不是 bug。要验证 AI 腿,必须构造同义词、相似词、乱序、缺必填、噪音表头等变体。
章节来源
docs/testing/fixtures/ai-llm-match-20261026 是一个自包含的测试环境:
generate.py 生成的 xlsx 或 csv 样本。manifest.json 描述每个用例的文件路径、类别、期望模板、变异方式和备注。run_tests.py 负责登录、打开案件、上传文件、触发智能清洗、轮询状态、读取数据库并写入结果。输出:results/ 目录下的 JSON 文件,包含上传结果、sheet 列表、job 信息、batch 信息和模板信息。
flowchart TD
Start["开发者运行测试"] --> Mode{"选择模式"}
Mode -->|生成数据| Gen["generate.py"]
Mode -->|执行测试| Run["run_tests.py"]
Gen --> Manifest["更新 manifest.json"]
Gen --> Samples["生成 bills/calls/edge 等样本"]
Run --> ManifestRead["读取 manifest.json"]
ManifestRead --> Upload["POST /dm/preFileUpload"]
Upload --> Clean["POST /aiClean/smartClean"]
Clean --> Status["轮询 /aiClean/smartClean/status"]
Status --> DBJobs["查询 ai_clean_job"]
Status --> DBBatch["查询 ai_clean_batch"]
Status --> DBTemplate["查询 ai_clean_template"]
DBJobs --> Result["写入 results/*.json"]
DBBatch --> Result
DBTemplate --> Result
图示来源
章节来源
generate.pygenerate.py 是测试数据的工厂。它不依赖外部人工准备样本,而是连接 PostgreSQL,读取系统模板元数据,再用 openpyxl 和 xlwt 生成 Excel 文件。
zsjz-ai。20261026,保证生成数据可重复。docs/testing/fixtures/ai-llm-match-20261026 下对应目录。manifest.json,使执行器能识别新用例。| 参数 | 含义 |
|---|---|
targeted |
生成账单、话单、跨行业和异常场景样本 |
full |
生成 table_info 全量模板的原表头与变体样本 |
all |
同时执行 targeted 与 full |
脚本内置了多类数据生成函数,例如姓名、身份证号、手机号、银行卡号、金额、日期、通话时长、借贷方向、通话方向、公司、支行、地址、航班、车次、机场、车站等。这些值用于模拟真实业务数据,避免样本过于简单导致匹配器误判。
flowchart TD
Entry["python generate.py <mode>"] --> ReadDB["连接 zsjz-ai 数据库"]
ReadDB --> Seed["设置随机种子 20261026"]
Seed --> SelectMode{"选择模式"}
SelectMode -->|targeted| Targeted["生成账单/话单/跨行业/异常样本"]
SelectMode -->|full| Full["生成 table_info 全量模板样本"]
SelectMode -->|all| Both["执行 targeted 与 full"]
Targeted --> WriteFiles["写入 fixtures 子目录"]
Full --> WriteFiles
WriteFiles --> UpdateManifest["提示更新 manifest.json"]
图示来源
章节来源
run_tests.pyrun_tests.py 是 LLM 模板匹配测试的主要入口。它封装了 HTTP 客户端、数据库查询和结果序列化逻辑。
| 命令 | 含义 |
|---|---|
python run_tests.py group1 |
按 manifest 分组定向执行 |
python run_tests.py group1 --upload-only |
只上传,不发起智能清洗 |
python run_tests.py group1 --limit 10 |
限制执行数量 |
python run_tests.py list |
列出分组 |
| 组件 | 职责 |
|---|---|
Client |
管理 requests.Session、登录、自动续 token、打开案件、请求 API |
db_jobs |
根据 sheet_id 列表查询 ai_clean_job |
db_batch |
根据 batch_id 查询 ai_clean_batch |
db_ai_template |
根据模板 id 查询 ai_clean_template 及其字段 |
upload_one |
上传单个文件并提取 sheet 信息 |
run_group |
加载 manifest、过滤用例、分批执行、汇总结果 |
| 接口 | 方法 | 用途 |
|---|---|---|
/auth/login |
POST | 获取认证 token |
/case/open |
POST | 打开目标案件 |
/dm/preFileUpload |
POST | 上传文件并返回 batchId、fileId、sheets |
/aiClean/smartClean |
POST | 发起智能清洗 |
/aiClean/smartClean/status |
GET | 查询批次状态 |
/gt/treeTablePage |
POST | 查询治理树数据页 |
sequenceDiagram
participant Dev as "开发者"
participant Runner as "run_tests.py"
participant API as "后端 API"
participant DB as "PostgreSQL"
Dev->>Runner: 运行指定分组
Runner->>API: POST /auth/login
Runner->>API: POST /case/open
loop 遍历 manifest 中的用例
Runner->>API: POST /dm/preFileUpload
API-->>Runner: 返回 fileInfo、batchId、sheets
alt --upload-only
Runner->>Runner: 保存上传结果
else 正常执行
Runner->>API: POST /aiClean/smartClean
Runner->>API: GET /aiClean/smartClean/status
API-->>Runner: 返回批次状态
Runner->>DB: 查询 ai_clean_job
Runner->>DB: 查询 ai_clean_batch
Runner->>DB: 查询 ai_clean_template
Runner->>Runner: 合并结果并写入 JSON
end
end
图示来源
章节来源
manifest.jsonmanifest.json 是测试夹具的索引。它定义了测试总数、分类计数以及每个用例的详细元数据。
| 字段 | 含义 |
|---|---|
generated_at |
清单生成时间 |
case_id |
默认测试案件 id |
counts |
各分类用例数量统计 |
files |
用例数组 |
| 字段 | 含义 |
|---|---|
test_id |
用例编号,如 BILL-01、CALL-02 |
category |
分类,如 bill、call、industry、edge |
path |
相对于 fixtures 根目录的文件路径 |
template_id |
参考模板 id |
expected_template_id |
预期模板 id |
mutation |
对原始表头的变异说明 |
sheet |
工作表名称 |
rows |
数据行数 |
notes |
人类可读说明 |
format |
文件格式,如 xlsx |
| 变异键 | 含义 |
|---|---|
| 普通键值对 | 将原表头名替换为新名字 |
_order: "reversed" |
倒置列序 |
_extra |
增加额外列 |
_drop |
删除某个列 |
_keep |
仅保留部分列 |
erDiagram
MANIFEST {
string generated_at
int case_id
}
COUNTS {
int bill
int call
int industry
int edge
int "full-exact"
int "full-variant"
}
FILE_ENTRY {
string test_id
string category
string path
int template_id
int expected_template_id
object mutation
string sheet
int rows
string notes
string format
}
MANIFEST ||--o{ COUNTS : "包含"
MANIFEST ||--o{ FILE_ENTRY : "包含"
图示来源
章节来源
前端 ESLint 位于 ai-electron/frontend/eslint.config.mjs,主要特点如下:
vue-eslint-parser,TypeScript 使用 @typescript-eslint/parser,JS 使用 @babel/eslint-parser。ban-ts-comment、ban-types、no-explicit-any、no-unused-vars 等。Prettier 集成,打印宽度 120,分号结尾,单引号,尾随逗号全开。
graph TB
Files["前端源码<br/>*.vue / *.ts / *.tsx / *.js"] --> ESLint["eslint.config.mjs"]
ESLint --> Vue["Vue 解析器"]
ESLint --> TS["TypeScript 解析器"]
ESLint --> JS["Babel 解析器"]
ESLint --> Prettier["Prettier 格式化规则"]
图示来源
章节来源
前端 Stylelint 位于 ai-electron/frontend/stylelint.config.mjs,主要特点如下:
stylelint-config-standard 和 stylelint-config-standard-less。postcss-html,LESS 使用 postcss-less,SCSS 使用 postcss-scss。global、deep、v-deep、v-global、v-slotted。与 Prettier 集成,禁用部分 Prettier 的 CSS 重写偏好。
graph TB
Styles["样式文件<br/>*.vue / *.less / *.scss / *.css"] --> Stylelint["stylelint.config.mjs"]
Stylelint --> Standard["标准规则"]
Stylelint --> Less["LESS 规则"]
Stylelint --> SCSS["SCSS 规则"]
Stylelint --> Vue["Vue 扩展规则"]
Stylelint --> Prettier["Prettier 集成"]
图示来源
章节来源
Electron 项目的 Vitest 配置位于 ai-electron/vitest.config.mts,当前行为是:
electron/**/*.test.ts。frontend/ 有独立前端工程,不在此处执行。需要特别注意:前端业务代码并未在 package.json 中看到显式 vitest 脚本,也没有在前端 package.json 中引入 vitest 依赖。因此当前前端测试能力较弱,更适合补充单元测试和页面级测试。
flowchart TD
Root["ai-electron/vitest.config.mts"] --> Include["匹配 electron/**/*.test.ts"]
Root --> PassNoTests["passWithNoTests=true"]
Frontend["ai-electron/frontend/"] --> Separate["独立前端工程<br/>无统一测试脚本"]
图示来源
章节来源
后端 Java 项目遵循 Maven 标准布局:
| 目录 | 含义 |
|---|---|
ai-server/src/main/java |
服务端主代码 |
ai-server/src/test/java |
服务端单元测试 |
ai-server/src/test/resources |
服务端测试资源 |
ai-gateway/src/main/java |
网关主代码 |
ai-gateway/src/test/java |
网关单元测试 |
ai-gateway/src/test/python |
网关相关的 Python 测试或辅助脚本 |
从 pom.xml 可见,后端依赖 spring-boot-starter-test 和 reactor-test,说明测试框架基于 Spring Boot Test,可能使用 JUnit、Mockito 及响应式测试工具。不过当前仓库中没有发现 surefire/failsafe 插件的自定义配置,也没有看到覆盖率插件(如 JaCoCo)的显式启用。
因此,当前后端测试约定更接近“默认 Maven 约定 + Spring Boot Test”,而不是一个强约束的测试工程。
graph TB
MainJava["src/main/java"] --> ServerMain["ai-server 主代码"]
MainJava --> GatewayMain["ai-gateway 主代码"]
TestJava["src/test/java"] --> ServerTest["ai-server 单元测试"]
TestJava --> GatewayTest["ai-gateway 单元测试"]
TestResources["src/test/resources"] --> ServerResources["ai-server 测试资源"]
TestPython["src/test/python"] --> GatewayPython["ai-gateway Python 测试"]
图示来源
章节来源
ai-server/src/test/java 或 ai-gateway/src/test/java 下创建对应包结构。HeaderScorerTest、AiTemplateMatcherTest。docs/testing/fixtures/ai-llm-match-20261026 下新增或修改样本文件。
bills/。calls/。edge/。retest/。manifest.json 中新增一条用例,填写 test_id、category、path、expected_template_id、mutation、sheet、rows、notes、format。python docs/testing/fixtures/ai-llm-match-20261026/run_tests.py <group>。docs/testing/fixtures/ai-llm-match-20261026/results/ 下的最新 JSON 结果。当前前端缺少统一的测试脚本。建议:
ai-electron/frontend 下新增单元测试目录,例如 __tests__/。package.json 中增加测试脚本,例如 test、test:watch、test:coverage。lint:eslint、lint:stylelint、type:check 纳入本地提交钩子和 CI。章节来源
当 LLM 模板匹配失败时,按以下顺序排查:
是否被既有匹配器直中
同表头或集合匹配成功的文件不会进入 AI 链路。这是设计行为。若目标是验证 AI,需要使用同义词、相似词、乱序、缺必填或噪音表头。
上传阶段是否误配模板
查看上传结果中的 template_id 是否与 expected_template_id 一致。如果既有匹配器把话单误判为通信登记信息,属于上传阶段问题,不是 AI 模型的问题。
AI 是否被触发
检查 matched_by 是否为 SCORE、HEADER_CACHE 或 LLM。如果既有条目直接走既有清洗,则不会出现 AI 判定日志。
是否被 verify 闸门拦截
如果模型选对模板但必填列未绑定或抽样错误率高,会进入 NEED_REVIEW。这说明防御机制生效,不代表匹配完全失败。
数据库结果是否一致
使用 run_tests.py 自带的 db_jobs、db_batch、db_ai_template 逻辑核对 ai_clean_job、ai_clean_batch、ai_clean_template。
是否是并发或模型超时问题
本地 Ollama qwen 模型可能只支持单会话并发。默认并发过高会导致重试、超时和批次卡住。
flowchart TD
Fail["LLM 匹配失败"] --> CheckDirect["检查是否既有直中"]
CheckDirect -->|是| DesignOK["符合设计:已有匹配器优先"]
CheckDirect -->|否| CheckUpload["检查上传模板是否误配"]
CheckUpload -->|误配| FixUploader["修复上传阶段匹配逻辑"]
CheckUpload -->|正确| CheckAICall["检查 AI 是否被触发"]
CheckAICall -->|未触发| CheckVerify["检查 verify 闸门"]
CheckAICall -->|已触发| CheckResult["检查结果 JSON 与数据库"]
CheckVerify -->|NEED_REVIEW| ReviewCase["人工复核或改进绑定"]
CheckResult --> MatchOK{"结果是否符合预期"}
MatchOK -->|是| Record["记录为已知差异或观察项"]
MatchOK -->|否| Investigate["调查匹配分数、模板覆盖率和模型输出"]
图示来源
| 编号 | 问题 | 修复方向 |
|---|---|---|
| F-01 | 交通银行同义表被错配为农商行模板,必填缺失导致 0 行入库 | 加强文件侧覆盖率门槛,收紧模糊词相似度规则 |
| F-01B | 导出话单变体被误判为通信登记信息 | 修复上传阶段部分匹配逻辑 |
| F-03B | AI 动态表方向字典整串写入单元格 | 修正方向字段归一化 |
| F-05 | 出口 B 新建 AI 表与既有模板同名,日志不可区分 | 使用 AI_ 前缀和导入备注区分 |
| F-06 | 二次 askLlm 返回越界序号 | 增加序号校验 |
| F-07 | 语义命中模板但确定性闸门拦截 | 改进必填列绑定或闸门判断 |
章节来源
lint:eslint、lint:prettier、lint:stylelint、lint:all、type:check。flowchart TD
Push["推送代码"] --> Install["安装依赖"]
Install --> TypeCheck["TypeScript/Vue 类型检查"]
TypeCheck --> ESLint["ESLint"]
ESLint --> Prettier["Prettier 检查"]
Prettier --> Stylelint["Stylelint"]
Stylelint --> UnitTests["后端 JUnit 单元测试"]
UnitTests --> Coverage["生成后端覆盖率"]
Coverage --> LLMSmoke["LLM 模板匹配冒烟测试"]
LLMSmoke --> Build["构建前端与 Electron"]
Build --> Report["生成构建报告"]
Report --> Release["发布前检查"]
图示来源
pnpm lint:all 在本地可运行。lint:eslint、lint:stylelint、type:check 加入 pre-commit 或 CI。manifest.json 变更进行 diff 检查,防止意外丢失用例。results/ 中关键结果做快照对比,但不把大体积二进制产物作为必要提交。建议发布前至少完成:
pnpm type:checkpnpm lint:eslintpnpm lint:prettierpnpm lint:stylelint章节来源
该仓库的测试体系以“确定性单测 + 端到端链路 + LLM 模板匹配夹具”为主轴。ai-template-match-test-cases.md 提供了测试范围和设计口径,ai-llm-template-match-bug-report-20261026.md 提供了缺陷复现与修复上下文,而 fixtures/ai-llm-match-20261026 下的 generate.py、run_tests.py、manifest.json 构成了 LLM 模板匹配测试的数据、执行器和索引。
前端代码质量工具链已经比较完善,但测试能力仍较弱;后端有 Spring Boot Test 基础,但缺少统一的覆盖率与 CI 配置。未来应优先补齐前端单元测试、后端覆盖率目标和稳定的 LLM 冒烟测试流程,使 AI 模板匹配链路既能被快速回归,又能稳定支撑生产数据清洗。