融合入口(POST /aiClean/smartClean)与前端三处接入已落地,离线单测 69 条全绿,但整条链路还没在真实环境里跑通过一次:单测只能证明「doClean 只发一次、判定先行」这类时序,证明不了三件真问题——
templateId/mainId/tableRuleList 能不能原样喂给既有清洗(报文契约是否真的对得上);DONE/PARTIAL(而不是卡在 JUDGING 或一路 NEED_REVIEW);id<0 分流里真的取到数。当前环境已就绪:后端 :8980/js/a 已启动并就绪,前端 vite :3100 已起,浏览器已登录(/case,账号「系统管理员」),dev PG 里 5 张 ai_clean_* 表都在。本计划需要写入真实数据(dev PG 元数据 + 该案件 DuckDB + 一次真模型调用),所以先取批准。
已核到一个必须先修的造数错误:既有解析的匹配源是 table_field.field_name_cn(GlobalCache.ALL_FIELDS_MAP / ALL_MATCHED_FIELDS_MAP,GlobalCache.java:200-209),硬条件是「表头必须含该模板全部 matched=1 列」(PreDataListener.matchTemplate:340-395,纯 CollUtil.containsAll,无阈值、无列数相等要求,且不做全半角/括号归一,唯一清洗是 Str2NullConverter:21 去 \t\r\n'^ + trim)。我之前那份 e2e_ok_trans.xlsx 是照抄 table_info.headers 造的,用错了源,不保证命中 —— 必须按 field_name_cn 重造。
192.168.0.103:5432/zsjz-ai)选一个 matched 列少、且必填列不苛刻的模板:
select t.id, t.main_id, count(*) filter (where f.matched=1) mc, array_agg(f.field_name_cn) filter (where f.matched=1) from table_info t join table_field f on f.table_id=t.id group by t.id having count(*) filter (where f.matched=1) between 4 and 12 order by mc limit 15
取一个模板,把它全部 matched 列名逐字写进 e2e_ok_trans.xlsx 第 1 行(多放几列自己造的列没关系,只要保证「表头 ⊇ matched 列」就能命中;全字段 ⊇ 表头则算完全命中)。e2e_messy_acct.xlsx(表头 账户号码/账户名称/交易发生日期/交易发生时刻/收入金额/支出金额/账户余额/对手户名/对方开户网点/资金用途备注,第 1 行、无合并区,数据含 ¥、全角逗号千分位、2026年1月7日、空列、末行「合计」)—— 它必须不被任何模板命中(跑之前先对全部模板的 matched 列做一次 containsAll 反向校验,避免白跑)。ai_clean_job / ai_clean_batch / ai_clean_template 的行数与最大 id。/case → 点案件「AI验证案件-20260915」的「打开案件」(就是你为端到端建的那个案)。该案件里已有旧上传/旧 AI 表,不清理,所以下面所有比对都必须按「本次 batchId + 跑前基线 id 集合」过滤,不能对全表计数。e2e_ok_trans.xlsx、e2e_messy_acct.xlsx(同一批次:第二个文件回传首个响应里的 batchId)。SHEET_TEMPLATE_MATCHED_FAIL,「智能」按钮仍可点(hasCleanableSheet),且不再要求 hasSuccessResult。POST /js/a/aiClean/smartClean 200,立即跳 /data/cleanProgress?fused=1&batchId=...;页面不再自动发出第二个 POST /dm/doClean(网络面板只应看到 smartClean)。0/1 往上涨,直到 stage 进 DONE/PARTIAL/FAILED 后停止轮询(2s 一跳、状态请求数应等于阶段数×若干,不应无限)。/aiClean/data/page(网络面板可见,aiTemplateId = -id),表头是中文列名、数据行有内容;再点一个既有正数节点,确认走的仍是 /gt/treeTablePage、行为与改动前一致。/data/cleaning?focusKey=<sheetId> 并定位到该 sheet。若本次没自然出现,这条改为只做一次代码级确认(读 handleReviewManual 与 cleaning.vue 对 focusKey 的消费),不改配置去强造。/tmp/backend.log:智能清洗:合并 N 个 AI 命中的表与既有匹配的表,doClean 只调一次 出现恰好 1 次;未找到根节点文件,无需清洗 不出现(说明没白发);SSE 关闭只发生在既有清洗收尾处一次。ai_clean_batch(按 batchId):stage 终态、sheet_total=2、matched_count>=1、ai_judged_count=1、existing_hit_count / ai_load_count / need_review_count / failed_count 与日志逐条对得上;need_review_detail 是合法 JSON 数组。ai_clean_job(按 batch_id):messy 那条的 exit_code(A/A2/A3/B)、matched_by、tier、confidence、llm_calls(必须是 1,用来验证「草稿复用不二次调模型」在真实链路上成立)、row_total/row_loaded、status。ai_clean_template 新增行(若走出口 B):table_name_en = ai_t{id}、header_md5 唯一未冲突、status=ONLINE、category 落在 FileCategoryEnum 值域内;对应案件 DuckDB 里 ai_t{id} 表存在且 select count(*) = row_loaded。govern_tree 中 id < 0 的节点数 = 该案件有数据的 AI 表数,且 data_count 与真 count 一致;正数节点数量与跑前一致(既有链路零回归)。stage ∈ {DONE, PARTIAL}。PARTIAL 可接受(真模型对陌生列名判成待人工是设计内行为),但要留下 need_review_detail 的原因文本作为 P1 改进依据。JUDGING → 看是否 Semaphore/CaseContextHolder 问题(日志里 AI 判定异常 的堆栈);doClean 出现两次 → 说明 A 组合并逻辑漏了,回读 dispatchExisting 与前端实际提交的 fileInfos;id<0 分支没生效或 dataPage 的 head/pages 结构与 extractTablePayload 不匹配;cost_ms/llm_calls 写进文档「实现进度」,不当作功能失败。mvn -o -pl ai-server test -Dtest='com.zsjz.ai.module.aiclean.**.*Test')→ 再走浏览器复验,不攒着。ai-server/QingJian/workspace/{caseId}/upload/{batchId}/:确认后只删本次 batchId 目录(不碰别的批次、不碰 QingJian/{caseId}/data.db 案件库)。ai_clean_job/ai_clean_batch/ai_clean_template/ai_clean_field/ai_clean_rule 里本次新行保留(是 P1 复放的样本),但把 id 列出来;如需回滚数据用现成 POST /aiClean/revert?jobId=,不手写 DELETE。git status 里那条 AD ai-server/QingJian/uy76tkp8/data.db(早前测试残留,已被 git add 进索引又在工作区删掉):提请确认是否 git restore --staged 摘掉;不擅自改索引。docs/design/ai-clean-parallel.md:把真实端到端结果(耗时、exit_code 分布、llm_calls=1 佐证、S1→S7 哪一步最先出问题)补进第十四节,并在「实现进度」里把「端到端未跑」这条划掉。ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiSmartCleanService.java、.../api/AiSmartCleanController.java、.../service/AiCleanService.java(judge / loadViaAiTemplate / dataPage)ai-frontend/src/case/views/data/upload.vue、cleanProgress.vue、index.vue、ai-frontend/src/case/api/govern/governApi.tsai-frontend/src/main/java/.../dm/pre/PreDataListener.java:260-281,340-395、common/cache/GlobalCache.java:200-209C:/Users/cc/AppData/Local/Temp/e2e/Gen.java(需按第一节的 matched 列改 OK_HEADERS 后重新生成)