smart-clean-test-cases.md 16 KB

智能清洗端到端测试用例(smart-clean-test-cases)

测试方式:浏览器实测(ZCode in-app browser),逐条执行、记录实际结果;发现 bug 即修复并重跑该条 + 相关回归条目。 测试账号:admin / admin123;前端 http://localhost:3100;后端 http://localhost:8980(`/js/a`)。 每次重启后端后必须:案件管理 → 打开案件(数据测试)→ 再继续。

测试文件:

  • 文件 A:周海--车辆登记信息.xls(5.6KB,sheet「机动车信息」模板匹配失败 → AI 判定 → 待人工)
  • 文件 B:周海--通信18905810269.xls(92KB,通信记录可匹配模板 → 系统既有清洗)
# 场景 步骤 预期 结果
① 上传解析 A 上传页注入文件 A 解析树出现、sheet「机动车信息」模板匹配失败、「智能」可用 ✅ / N/A
② 上传解析 B 上传页注入文件 B 解析树出现、sheet 匹配模板成功、「智能」可用 ✅ / N/A
③ 混合上传 同批注入 A+B 树中两文件、状态各自正确 ✅ / N/A
④ MD5 去重回归 再次上传文件 A(不删记录) 后端跳过解析返回 null(原行为),页面无新树 ✅ / N/A
⑤ AI 判定推进 选 A 点「智能」→ 进度页 AI 卡判定 0/1→1/1,阶段推进 ✅ / N/A
⑥ 待人工结论 判定完成 待人工 1、失败 0、阶段 DONE ✅ / N/A
⑦ 处理日志 查看处理日志 「融合清洗已启动」「开始处理:文件 A」两条 ✅ / N/A
⑧ AI 卡列表布局 待人工 ≥4 条时查看 卡内 ≤3 条 + 「查看全部 N」抽屉;抽屉含原因说明文案 ✅ / N/A
⑨ 判定详情 抽屉点「详情」 弹窗:原因/置信度五维/抽样统计/逐列校验/预览样本/模型草稿 ✅ / N/A
⑩ AI 重新判定 抽屉点「AI 重判」 受理成功提示;约几十秒后清单刷新出新结论 ✅ / N/A
⑪ 既有路径推进 选 B 点「智能」 无 AI 表 → doClean 推进 → 100% → 完成态 ✅ / N/A
⑫ 既有入库验证 完成后查数据 通信记录表有数据 ✅ / N/A
⑬ 混合批次 A+B 点「智能」 B 交既有清洗一次 doClean、A 待人工、批次 DONE ✅ / N/A
⑭ 混合互不影响 验证数据 B 已入库、A 待人工,互不影响 ✅ / N/A
⑮ 清洗页有数据(核心 bug) 进度页点「去人工确认」 清洗页树出现、sheet 被选中、预览数据加载 ✅ / N/A
⑯ 清洗页清单可见 看左栏 「待人工确认」面板含该表、原因、置信度 ✅ / N/A
⑰ 清单「处理」定位 点「处理」 树定位到目标 sheet、选中、预览加载 ✅ / N/A
⑱ 仅洗当前表 配模板规则后「导入数据」 头部出现「仅清洗当前表」徽标;提交树只含该 sheet;清洗完成 ✅ / N/A
⑲ 清单消项不丢 清洗后回清洗页 该项已消项(SUPERSEDED),其余待人工仍在 ✅ / N/A
⑳ 清洗页内详情 清单点「详情」 详情弹窗正常 ✅ / N/A
㉑ 清洗页内重判 清单点「AI 重判」 受理成功、清单轮询刷新 ✅ / N/A
㉒ 上传页行级手动清洗回归 上传页行级按钮 清洗页正常(原 sessionStorage 路径) ✅ / N/A
㉓ 上传页整页手动清洗回归 上传页「手动清洗」按钮 正常跳转清洗页 ✅ / N/A
㉔ importList 重新清洗回归 导入管理「重新清洗」 reClean=1&fileId 路径正常 ✅ / N/A
㉕ 超时出口改手动清洗 融合页「改为手动清洗」 带 batchId → 清洗页按批次重建树 ✅ / N/A
㉖ Skip 入明细 构造 Skip(如删源文件后清洗) 清单出现 Skip 项、计数与条数一致 ✅ / N/A
㉗ jobs 案件隔离 切换案件调 /aiClean/jobs 只返回当前案件的 job ✅ / N/A
㉘ 重跑不重判待人工 有待人工时再点「智能」 日志显示沿用待人工、无新的模型调用 ✅ / N/A
㉙ 重判命中既有模板 重判后结论变化 ExistingHit → dispatchExisting 走 doClean ✅ / N/A
㉚ SSE 断连/刷新恢复 进度页刷新 restoreImportProgress 恢复进度继续 ✅ / N/A

执行记录

测试共执行 3 轮混合批次 + 多轮单项验证(浏览器实测)。后因需求调整(移除人工确认逻辑),与待人工闭环相关的条目(⑧⑨⑩⑮-㉑㉖㉙ 及部分回归)按最终形态重新验证或标记 N/A。

已修复并验证的问题

  1. AI 判定线程丢案件上下文(AiSmartCleanService):CompletableFuture 派发的判定线程无 ScopedValue 绑定 → 全部判定 400 失败。修复:判定任务包 CaseContextHolder.runWith。✅
  2. 模型调用总时长无上限(LlmService):Reactor timeout 是片间超时,本地模型慢速流式输出 35 分钟不触发。修复:blockLast 总时长兜底(匹配调用 180s)+ 提示词要求简短推理(判定从 35min+ 降到 ~60s)。✅
  3. /aiClean/jobs 跨案件越权:加 caseId 过滤。✅
  4. Skip 结论计数与明细不符:Skip 也写入明细。✅
  5. 清洗页树兜底:sessionStorage 被流程消费后可按 focusKey/batchId 从 /dm/getFiles 重建树(修复「人工确认页无数据」同源问题,超时出口「改为手动清洗」也依赖它)。✅

最终形态(移除人工确认后)验证

  • 混合批次(A 车辆登记 + B 通信记录):B 三表走系统清洗成功(272 条),A 判定后进入「无法清洗」;批次 DONE、失败 0。✅
  • AI 卡指标更名「无法清洗(表)」、列表纯展示(无任何跳转/操作按钮),展示原因。✅
  • 清洗页不再有待人工工作清单面板。✅
  • 上传页行级/整页「手动清洗」、importList 重新清洗等旧入口不受影响。✅
  • MD5 去重还原后重复上传仍被拦截。✅

统一展示改造(AI 链路并入主卡,浏览器实测 ✅)

  • 取消独立「AI 识别」卡片:进度环按系统清洗/AI 各自处理的 sheet 数加权合成一个总进度; AI 判定期间步骤条推进到「清洗入库」。
  • AI 事件全部并入处理日志:AI 识别完成并入系统清洗、AI 新建表入库中、逐条「AI 无法清洗:表名(原因)」、 阶段完成统计;混合批次实测日志顺序与内容正确。
  • 完成态描述带出「另有 N 个表 AI 无法清洗」提示;超时出口「改为手动清洗」与 「查看导入结果」并入主卡展示。
  • 实测混合批次(B 系统清洗 272 条 + A 无法清洗 1 条):单卡从 0% 推进到完成态,全程无独立 AI 卡。

待人工确认逻辑(已按需求移除)

  • 相关代码(清单面板/详情弹窗/重判/resolve 接口/消项钩子/仅清洗当前表模式)已全部移除; git 历史中保留可随时恢复。
  • ai_clean_job 仍以 NEED_REVIEW 状态记录「无法清洗」结论(原因可查),仅不再引导人工处理。

4 级降级专项验证(2026-10-11)

逻辑梳理(已逐行核实代码)

上传(L1 判定):upload.vue → /dm/preFileUpload → PreDataListener:每 sheet 建预览表 raw_{sheetId}、默认「模板匹配失败」;前 20 行内 matchTemplate(模板全字段包含 + 必填字段包含)→ 命中打 templateId/mainId/funcRegx/lineNo,「未命中」留给 AI 链路。

智能清洗(AiSmartCleanService.run,按降级顺序串行):只接管 templateId==null 的 sheet。

级别 判定 落地
1级 上传时已匹配模板 静默趟 doClean → 业务表
2级 AI 三腿匹配(表头指纹 → 归一化打分≥0.90 → LLM top-12 置信≥0.6)命中既有模板且 STRONG 等待 1级 Latch → 收口趟 doClean → 业务表
3级 命中 AI 模板库(复用已有 ai_t 表,零模型)或完全无候选(按文件自身表头建表,全 TEXT)或 WEAK 弱匹配 loadViaAiTemplate 建表/并入 → 流式写数 → ONLINE
4级 硬失败:需结构归一(合并单元格/多级表头)/ 无可用列绑定 / 置信度不过放行线 / 表头为空 只标记 NEED_REVIEW,不写任何数据表,原因进 need_review_detail + 处理日志
  • 日志绑定:行级失败/未配模板 → clean_error_log(fileId+sheetId+batchId);每 sheet → ai_clean_job;批次 → ai_clean_batch。
  • 治理结果:AI 表经 govern_tree 负数 id 节点 → /aiClean/data/page 分流取数(不写 table_info/table_field, AiCleanStore 有意为之,防被既有匹配器命中)。
  • AI 表同表头二次清洗:header_md5 唯一键下复用已有模板与动态表(存档规则零模型),数据按 job 并入/可撤销。

测试数据(ai-electron/frontend/public/__test__/)

文件 构造 预期
C-短信表头同义改写.xlsx 文件B短信表头改3列同义词、表头在第1行 2级:AI 语义命中既有模板 → 出口A
D-人员信息含噪声列.xlsx 人员信息+3个噪声列 观察:LLM 置信≥0.6→出口A,<0.6→出口B
E-星际航运清单.xlsx 全新表头(星际航运) 修复前4级无候选 → 修复后3级自建表
E2/E3-星际航运清单续/三.xlsx 同表头不同内容 3级:指纹直通复用已有 ai_t 表
F-合并单元格表头.xlsx 合并单元格标题+第2行表头 4级:需结构归一
G-短信必填空值.xlsx 同义表头+本机号码80%空值 4级:置信度不过放行线

用例与结果(浏览器实测 + 接口断言)

# 场景 预期 结果
A1 文件B上传 3 sheet 模板匹配成功(tpl 23/21/1) ✅
A2/A3 文件A+C混合上传 A 匹配失败、C(改表头)匹配失败,各自状态正确 ✅
B1 仅文件B智能清洗 批次 DONE、matched=3、aiJudged=0(守卫:已匹配的绝不插手) ✅
B2 清洗日志绑定 cleanLog/list 按 batchId 查,记录含 fileId/sheetId/batchId ✅
C1 2级 AI 语义匹配 job:exit=A、by=LLM、STRONG、置信0.78、refTpl=21;落库 21条 failNum=0 ✅(修复后)
D3 完全无候选 修复前:NEED_REVIEW「没有合适的模板」(缺陷确认);修复后:3级自建表,动态表 5行×6列(文件自身表头,全 TEXT) ✅(修复后)
D2 同表头二次清洗 修复前:指纹直通仍新建模板 → 撞 uk_ai_clean_tpl_md5 唯一键 FAIL(缺陷确认);修复后:HEADER_CACHE 复用同模板、llmCalls=0、数据并入(5+4=9行) ✅(修复后)
E1 4级·结构归一 文件A/文件F → NEED_REVIEW「该表块需要结构归一…」,处理日志可见,不写任何表 ✅
E2 4级·置信度闸门 文件G → NEED_REVIEW「置信度未达出口 A 的放行线」(perColErrorRate=0.8/rowYield=0.2) ✅
E3 4级落地断言 job 状态/批次计数/need_review_detail/前端日志三处一致;aiLoad=0 ✅
F1 templateId 非空零 AI 判定 同 B1(aiJudged=0) ✅
F3 治理结果链路 治理树含 AI 节点(负id,type=ai_table,count 正确);/aiClean/data/page 取数带表头行;治理任务重建后由 GovernService:380 calcAiTree 补挂(代码走查) ✅
串行 按级 1→2→3 串行 后端日志「按降级顺序串行清洗 1级表=N → 2级表=M」;单测断言先 doClean(true) 后 doClean(false) 再 LOADING_AI ✅
噪音 无「未配置模板」误报 修复前混合批次会为 NEED_REVIEW 表记「未配置模板!」;修复后 0 条 ✅
按钮 进度页无按钮 完成态页面无「查看导入结果」(截图目视 + DOM 断言 buttons=[]) ✅

本轮修复(已随验证闭环)

  1. 出口 A 落库断链(P0):AI 命中既有模板的表上传时没有 line_no,StreamingEtlListener:120 首行 NPE, 数据零落库但批次仍报 DONE。修复:ExistingHit 携带表头行号,dispatchExisting/ExistingTemplateDispatcher 回填 lineNo;复测 21 条全部入库。⚠️ 已知限制:doClean 阶段失败仍不会回写批次计数(与旧手动清洗行为一致)。
  2. 完全匹配不到→自建表(需求):judge 无候选分支改为按文件自身表头建兜底动态表(全 TEXT), loadViaAiTemplate 兼容 draft 为空;4级只留给硬失败。needsStructure 提前到无候选判定之前(结构问题优先定性)。
  3. 同表头指纹复用(P1):指纹直通命中 AI 模板库时复用已有模板与动态表(读回存档 ai_clean_rule,零模型调用), 不再新建模板撞 uk_ai_clean_tpl_md5;0 行失败不删复用表。
  4. 按降级顺序串行清洗(需求):拆分 1级/2级两趟 doClean,趟间 Latch 排队;首趟静默(不推终态/不关 SSE/不触发治理), 末趟收口;NEED_REVIEW/Skip 表不进任何一趟(顺带消除「未配置模板!」噪音日志)。
  5. 进度页移除「查看导入结果」按钮(需求):两处入口删除,handleViewResult 清理; 「查看错误日志」「改为手动清洗」为条件功能按钮,暂保留。

新增/调整单测

  • SelfHeaderPlanTest(3 用例):兜底绑定逐列自绑/全 TEXT 计划/空表头转人工。
  • AiSmartCleanServiceTest:原「合并一次 doClean」断言改为「1级静默趟 → 2级收口趟」时序断言 (cleansByLevelInOrder),并断言出口 A 必须回填 lineNo。
  • aiclean 模块 76 用例全绿(5 跳过为真连 LLM 的活测试)。

遗留观察

  • 出口 A 清洗中 CallDataCleaner 对个别列报 FunException(清洗函数 pattern 为 null,列级被清洗器内部消化, failNum=0 数据完整入库)——既有 34 策略的函数配置问题,非本轮引入,建议后续核查对方号码列的清洗函数装配。
  • 治理任务全量跑一遍(验证 truncate govern_tree 重建后 AI 节点补挂的实测闭环)未执行,由代码走查 GovernService:380 与 calcAiTree 幂等性保障;日常治理后可观察树上 AI 表节点确认。

完成跳转改造:进度全 SSE 推送,去轮询(2026-10-11 下午)

问题

清洗完成后没有跳转到结果统计页。根因:完成判定 isComplete 只看 SSE 清洗进度 progress, 而纯 AI/4级批次没有 doClean,SSE 进度永远不会到 100%(进度环显示的 100% 是另一个合成值); 此前靠「查看导入结果」按钮兜底,按钮移除后即卡死在「任务执行中」。

改造(按需求「不要轮询,全部 SSE 推送」)

  • 后端:AiSmartCleanService 每判定一张表、每个阶段切换、每张 AI 表入库、批次终态都推送 状态快照(cmd 103,SeeDataDTO.payload 携带 SmartCleanStatusVO);终态推送顺序为 统计(cmd 101,100%)→ 快照(cmd 103)→ 治理宣告(cmd 102,开启治理时), 保证治理启动出现在全部清洗动作之后。DmService 各趟 doClean 全静默(收口消息统一由终态推送发出, 清理副作用照常执行)。
  • 前端:删除 2 秒轮询;cmd 103 驱动进度环/日志/完成判定;打开页面(含刷新恢复) 仅做一次性状态同步对齐初始值;SSE 断连且批次未终态时给出「改为手动清洗」出口。
  • 修复过程中发现并修复:完成判定/自动跳转 watch 在 setup 早期急切求值 aiStageFinished(TDZ)导致页面白屏。

验证(浏览器实测,无轮询)

  • 纯 AI 批次(D-人员信息含噪声列):提交后进度页 SSE 推进 → 判定完成约 130s → 页面未刷新自动跳转到 /data/index?tab=files&mode=smart,列表显示「导入成功 16 条」。✅
  • 刷新恢复:停留在已完成的进度页刷新 → 一次性同步拿到终态 → 自动跳转。✅
  • 秒完批次(C+E3,均为头部指纹直通复用 AI 模板):提交 → 进度页 → 自动跳回统计页。✅
  • 指纹复用跨批次:C 与 G 同表头,G 批次建的 AI 模板被 C 秒级复用(HEADER_CACHE,零模型调用)。✅
  • 回归:aiclean 模块 76 个单测全绿(静默趟断言更新为「两趟全静默」)。