cc fc072c5af9 docs(aiclean): 补充 2026-10-22 界面验收用例、样本与截图 преди 3 дни
..
fixtures fc072c5af9 docs(aiclean): 补充 2026-10-22 界面验收用例、样本与截图 преди 3 дни
screenshots fc072c5af9 docs(aiclean): 补充 2026-10-22 界面验收用例、样本与截图 преди 3 дни
README.md fc072c5af9 docs(aiclean): 补充 2026-10-22 界面验收用例、样本与截图 преди 3 дни
generate_fixtures.py fc072c5af9 docs(aiclean): 补充 2026-10-22 界面验收用例、样本与截图 преди 3 дни

README.md

AI 数据清洗 E2E 验收(2026-10-22)

1. 目标

验证本轮改造在真实案件、真实数据源上的表现:

  • 行级失败可追溯:每一行为什么没进去、因为哪一列、原值(脱敏)。
  • 必填列坏值整行拒绝;非必填坏值置空、行照常入库。
  • 成功 / 失败 / 警告 / 未处理条数口径正确。
  • 日期、时间、金额、数字、电话、身份证、卡号的类型化与入库。
  • CSV 不再被探测阶段直接 Skip;模板复用不会因列序变化写错列。
  • AI 动态表在结果页/数据页可查,类型正确。

2. 环境

项 值
后端 ai-server,http://127.0.0.1:8980/js/a
前端 http://localhost:3100
案件 新建 AI清洗验收-20261022(案件 id 10)
登录 开发环境默认 admin
迁移 已执行 sql/ai_clean_row_stats.sql(否则 ai_clean_batch.loaded_rows 等列不存在,智能清洗 500)

自动化环境限制:ZCode 内置浏览器不支持系统文件选择框。样本通过页面同源的 POST /dm/preFileUpload(携带页面登录 token)注入;随后 智能清洗、导入文件列表、 数据页、失败明细弹窗全部在界面里操作/核对。在普通浏览器里可直接用「选择文件」。

3. 测试样本

样本在 fixtures,可用 generate_fixtures.py 重新生成。

文件 内容 目的
01_self_built_types.xlsx 标题行 + 空行 + 9 列表头 + 7 行数据(含各类脏值、空行隔断) 自建表类型推断、日期/金额/数字/证件转换、row_no 缺口
02_reordered_headers.xlsx 与 01 同表头但倒序 模板复用必须同序(单测覆盖,UI 未跑)
03_messy_title_blank.xlsx 单格标题 + 空行 + 表头 + 4 行数据(其中一行坏金额/日期) 表头定位、标题/空行不阻塞、坏值置空
04_csv_types.csv 与 01 同数据,UTF-8 CSV CSV 探测/入库;可选列脏值只置空
05_required_bad.xlsx 同 9 列表头,20 行;第 1 行身份证 12345 非法 必填列坏值整行拒绝 + 失败明细
06_csv_clean.csv 同 9 列表头,7 行全合法 CSV 正常入库

01 的 9 列与脏值覆盖:

  • 姓名:张三/李四/...
  • 身份证号:18 位合法、15 位老证、坏校验位、12345
  • 手机号:+86 139-0000-1111、123
  • 银行卡号:6222 0202 ...、科学计数法 6.22202E+15
  • 交易日期:ISO、2026/1/6、2026年1月7日、2026-02-30、Excel 序列号 45000、紧凑 20260108
  • 交易时间:datetime、ISO、纯时间 10:30、25:00、epoch 1704067200
  • 金额:1,234.50、-100.00、(200.50)、约100元、¥2,300.5、100元
  • 数量:整数、1.5、abc

4. 用例与结果

TC-01 自建表类型推断与脏值置空(01)

  • 操作:上传 01 → 智能清洗(无候选 → 出口 B 自建表)。
  • 预期:7 行入库;无效的非必填值置空;日期/金额/数量落真实类型。
  • 实测:loadedRows=7, rejectedRows=0, warned=2, selfBuiltCount=1;AI 表「经营者信息」7 行。
  • 类型实测(ai_clean_field):
    • c1 身份证号 ID_CARD_NUMBER、c2 手机号 PHONE、c3 银行卡号 ACCT_CARD_NO(物理 TEXT)
    • c4 交易日期 DATE、c5 交易时间 DATE_TIME(TIMESTAMP)
    • c6 金额 MONEY(DECIMAL(18,2))、c7 数量 NUMBER(DECIMAL(38,6))、c8 备注 TEXT
  • 数据实测:
    • 王五:身份证/手机/卡/纯时间 → NULL;日期 2026-01-07;金额 (200.50) → -200.50;数量 1.5。
    • 赵六:坏日期/时间/金额/数量 → NULL;手机 13800002222、卡号正常入库。
    • 钱七:45000 → 2023-03-15;1704067200 → 2024-01-01 08:00:00;¥2,300.5 → 2,300.50。
    • 孙八:20260108 → 2026-01-08;100元 → 100.00。
    • 周九:空行之后仍入库,row_no 保留原文件物理行号。
  • 截图:ai-table-40-rows.png(含 01 的 7 行)

TC-02 必填列坏值整行拒绝 + 失败明细(05)

  • 前置:把 01 生成的 AI 模板字段 c1(身份证号)临时改为 required=1。
  • 操作:上传 05(20 行,第 1 行身份证 12345)→ 智能清洗(指纹命中同一 AI 模板,出口 B 复用)。
  • 预期:19 行入库、1 行拒绝;失败明细能看到行号/列/原因/原值。
  • 实测:loadedRows=19, rejectedRows=1, unprocessedRows=0;job rowTotal=20, rowLoaded=19, rowRejected=1。
  • 失败明细接口:

    {"row_no":2,"blocking_col_index":1,"blocking_col_name":"c1",
    "reason":"REQUIRED_TYPE_INVALID",
    "errors_json":"[{\"colIndex\":1,\"column\":\"c1\",\"reason\":\"REQUIRED_TYPE_INVALID\",\"value\":\"12345\",\"blocking\":true}]"}
    
  • 界面:导入文件列表 → 展开文件 → 工作表行「导入失败 1」→ 弹窗显示:

    • 文件 / 工作表、成功 19 / 失败 1
    • 行号 2、阻塞列 c1(列 1)、原因「必填列类型不合法」、原值预览 12345
  • 截图:ai-reject-modal.png

  • 后置:required 已重置为 0,避免影响后续样本。

TC-03 CSV 脏数据:CSV 不再 Skip,可选脏值只置空(04)

  • 第一次跑:CSV 探测成功、指纹命中 AI 模板(headerScore=1.0),但抽样校验因身份证列 2/7=28.6% 非法(非必填)判 待人工。
  • 本次发现并修复:非必填列错误不应计入抽样硬门,否则和「非必填置空、行照常入库」冲突。 修复后重跑:loadedRows=7, rejectedRows=0, warned=2,复用同一 AI 模板。
  • 结论:CSV 探测/入库链路正常;可选脏值进入 rowWarned/detail.load.optional,不阻塞行。

TC-04 干净 CSV 正常入库(06)

  • 操作:上传 06 → 智能清洗(指纹命中 AI 模板)。
  • 实测:loadedRows=7, rejectedRows=0, selfBuiltCount=0。
  • 结论:CSV 不再被 SheetProbe 用 POI 拒之门外。

TC-05 标题行 + 空行 + 表头定位(03)

  • 操作:上传 03 → 智能清洗(无候选 → 自建表「人员交易」)。
  • 实测:loadedRows=4, rejectedRows=0;坏金额/日期两列置空,行保留。
  • 数据页:AI 表「人员交易」4 行。
  • 结论:标题行(1 格)、空行、行号缺口均未阻塞;表头定位正确。

TC-06 统计口径与界面列表

最终导入文件列表(import-list-5-files.png):

文件 导入成功 导入失败
06_csv_clean.csv 7 0
03_messy_title_blank.xlsx 4 0
04_csv_types.csv 7 0
05_required_bad.xlsx 19 1
01_self_built_types.xlsx 7 0

数据页(ai-table-40-rows.png):

  • 「经营者信息」40 行 = 01 的 7 + 05 的 19 + 06 的 7 + 04 的 7。
  • 「人员交易」4 行。
  • 日期/时间/金额/数量在界面上按类型展示,脏值显示为空而不是空白字符串。

TC-07 单测覆盖

mvn -pl ai-server -Dtest='com.zsjz.ai.module.aiclean.**.*Test,com.zsjz.ai.module.dm.**.*Test' test: 192 个用例通过(5 个 live 跳过)。覆盖:

  • 行级失败原因、必填整行拒绝、非必填置空计数
  • AiValueCoercer 日期/时间/金额/数字/电话/身份证/卡号格式矩阵
  • HeaderScorer 同义词/列序/单位/形状/必填覆盖与出口 A 闸门
  • sameOrderedHeaders 复用安全
  • 真 DuckDB 的类型化入库、reject 表分页/清理、先写后删

5. 本次发现并修复的问题

  1. 非必填脏值被当成硬错误:AiRuleVerifier 用 cleanRow 的全部错误计算列错误率, 可选身份证列 28.6% 非法导致整表转人工。修复:cleanRow 只回填阻塞错误, cleanRowDetailed 仍保留全部错误供 warned/明细统计(提交 c4b167b)。
  2. 单 sheet 文件的失败入口:文件行原来没有 aiJobId/cleanMode,点「导入失败」只能走旧 clean_error_log。修复:单 sheet 文件把 sheet 的 AI 任务信息带到文件行(提交 c4b167b)。
  3. 迁移缺失:ai_clean_batch.loaded_rows 等新列未建时智能清洗 500;必须执行 sql/ai_clean_row_stats.sql。

6. 未覆盖 / 已知限制

  • LLM 匹配/打分/向量:本次 UI 验收为了速度临时关闭了向量召回和本地 LLM (本地模型单次调用可能数分钟,DashScope 嵌入额度已耗尽)。匹配逻辑由 HeaderScorerTest / AiTemplateMatcherTest 覆盖;恢复 LLM 后建议再跑一次真实模型样本。
  • 02_reordered_headers.xlsx:需要 LLM 重新绑定,本次 UI 未跑;sameOrderedHeaders 单测已钉住「列序变化不复用旧 colIndex」。
  • 多块 sheet / 合并表头:本轮未实现,仍只处理 primaryBlock;02 之外的乱序/合并样本 待 Phase B 后续。
  • 跨案件同表头:header_md5 全局唯一键尚未改案件作用域,本轮未做双案件并发验收。
  • 界面「导入失败」在文件行(多 sheet 文件)仍走旧日志;单 sheet 已走 AI 明细。

7. 复现步骤

  1. 执行 sql/ai_clean_row_stats.sql。
  2. 启动后端 mvn -pl ai-server spring-boot:run;启动前端 pnpm dev。
  3. 新建并打开案件,上传 fixtures/ 下样本(普通浏览器可直接选择文件)。
  4. 点「清洗数据」→「智能清洗」,或调用 POST /js/a/aiClean/smartClean。
  5. 打开「案件数据 → 导入文件」,核对成功/失败数;展开文件点「导入失败」看明细。
  6. 打开「案件数据 → 导入数据」,核对 AI 动态表行数、列名与日期/金额/数字展示。
  7. 必填拒绝用例:先让 01 生成 AI 模板,再执行

    update ai_clean_field set required=1
    where ai_template_id = :tpl and column_name = 'c1';
    

上传 05_required_bad.xlsx 并智能清洗;验收后把 required 重置为 0。