plan-session_1bc2a9baf5931f11f8e15fbbfd50834d.md 15 KB

AI 数据清洗改造计划(修订版)

已确认口径:必填列任何数据错误(空值/类型不合法/函数执行异常/超长溢出)→ 整行拒绝;无模板自建表照常入库,但单独统计,不计入模板匹配成功;每一行为什么没进去必须可追溯。

一、评审结论(摘要)

匹配率:表头归一化删除括号语义(金额(元)/金额(万元)、账号(本方)/账号(对方) 同指纹);打分集合匹配不看列序、无同义词、短模板难破 0.90;AI 模板复用不校验 md5/列序、不跑 verify;header_md5 全局唯一但物理表案件级;同批次同表头撞唯一键;列数不一致直接 drop 共享表;模型自评 0.6 可把 0 分候选升 STRONG;CSV/加密/多块/合并单元格探测缺口大。

入库率:出口 B 无日期/时间转换(出口 A 有 FunDate/FunTime/FunDateTime);DATE 收到 datetime 串、TIME 被猜成 DATE_TIME,DuckDB cast 失败整批 FAIL;金额不支持负数/括号/币种;NUMBER→INTEGER 溢出;一个坏值让整批 INSERT 失败;required 来自模型、必填失败整行丢且不计数;异常路径不清理;统计把未清洗算成功。

类型匹配率:类型只按英文字段名关键词猜;ColStat.shape 只进提示词没进建表;出口 B 忽略 table_field 类型;directionConf/fieldLen 丢失;FunMultiplier 整数 NPE、小数只乘整数部分。

二、Commit 1(Phase A):类型化、行级诊断与入库止血

A0. 行级失败诊断与成功/失败统计(本次新增重点)

A0-1 行清洗结果模型

  • 新增 ColumnError:colIndex / columnName / reason / valuePreview / blocking。
  • reason 枚举:REQUIRED_EMPTY、REQUIRED_TYPE_INVALID、REQUIRED_FUNC_ERROR、REQUIRED_LENGTH_OVERFLOW、OPTIONAL_TYPE_INVALID、OPTIONAL_FUNC_ERROR、OPTIONAL_LENGTH_OVERFLOW。
  • AiRecordCleaner 新增 cleanRowDetailed(...):遍历所有列、收集全部错误,不再遇到第一个必填错误就返回;任一 blocking=true → 整行拒绝;只有非必填错误 → 单元格置 null、行照常入库。
  • 函数链异常不再被 FunProcess.onException 静默吞掉:在 AI 侧捕获 FunException,记录 FUNC_ERROR;必填列 → 行拒绝,非必填 → 列置空。
  • 必填规则:空值、日期/金额/数字/身份证/电话/卡号解析失败、函数异常、长度/精度溢出,全部整行拒绝(已确认)。

A0-2 失败行落库(可逐行追溯)

  • 在案件 DuckDB 新增 ai_clean_reject(懒建表,CREATE TABLE IF NOT EXISTS):

    id, job_id, case_id, file_id, sheet_id, block_no, row_no,
    blocking_col_index, blocking_col_name, reason,
    errors_json,          -- 该行所有列错误(列名/原因/脱敏值预览)
    create_time
    

    索引 (job_id, row_no)。

  • 每个被拒绝的行写一条记录;blocking_col_index/name 是第一个阻塞列,errors_json 保留该行全部列错误,能回答“因为哪一列没进去”。

  • 非必填错误(行已入库但单元格为空)默认只做逐列聚合计数 + 样例,不逐行落库;配置 zsjz.ai-clean.reject-log-optional=true 可逐行记录。

  • 批量写入(攒批 flush),避免逐行 SQL;任务失败时按 ai_job_id 清理半截记录;删除文件/撤销任务时同步清理。

  • 脱敏:身份证/手机号/卡号默认掩码(保留首尾),配置 zsjz.ai-clean.mask-sensitive=false 可关闭;值预览长度可配。

A0-3 日志

  • 每行拒绝的权威记录在 ai_clean_reject;应用日志只打前 N 条(默认 20)避免刷爆: WARN AI reject job=... file=... sheet=... row=42 col=3(金额) reason=REQUIRED_TYPE_INVALID value=1.2万。
  • 每个 sheet 结束打一条汇总:source / loaded / rejected / warned / topReasons / topColumns。
  • 配置 zsjz.ai-clean.reject-log-sample(默认 20)、reject-log-all(默认 false,调试用)。
  • 写入异常(BatchWriter/连接):记录 jobId、当前 rowNo、异常信息;该行记 WRITER_ERROR,剩余行计 row_unprocessed。

A0-4 成功/失败条数口径

  • ai_clean_job 新增:row_total(表头后非空源行)、row_loaded、row_rejected,新增 row_warned、row_unprocessed。
  • 成功任务:row_loaded + row_rejected = row_total;row_warned 是已入库但含非必填错误的行数(不重复计入失败)。
  • 失败任务:半截新行按 job 清理,row_loaded=0(最终表没有新增行);row_rejected=已处理且被拒的行;row_unprocessed=row_total-已处理行。
  • job.detail.load 写 JSON:reasonCounts、columnRejectCounts、optionalErrorCounts、topExamples(脱敏)。
  • ai_clean_batch 新增:loaded_rows、rejected_rows、self_built_count;SmartCleanStatusVO 下发。
  • 终态/getFiles:SUCCESS 用 rowTotal/rowLoaded/rowRejected;NEED_REVIEW/SKIP/FAIL 不再用 file_info.dataNum 计成功,成功数记 0;自建表单独展示。
  • SSE 终态:completed = 既有清洗成功行 + AI rowLoaded;failed = 既有失败行 + AI rowRejected;未清洗单独计数,不混入成功。

A0-5 查询接口与前端

  • 新增 GET /aiClean/job/rejections?jobId=&page=&size=&reason=,分页返回 row_no / 列名 / 原因 / 脱敏值 / errors_json。
  • jobs 接口返回 rowTotal/rowLoaded/rowRejected/rowWarned 与 detail.load 汇总。
  • 前端:AI 表结果页/导入文件管理增加「失败明细」抽屉或表格(复用现有表格组件),显示成功/失败/警告条数与逐行原因;先做只读查看。

A1. 统一类型转换器

新增 AiValueCoercer,AiRecordCleaner 调用,输出 Java 类型:

  • DATE → LocalDate、TIME → LocalTime、DATE_TIME → LocalDateTime;支持 ISO、yyyy/M/d、yyyy.MM.dd、yyyyMMdd、中文日期、带时间/ISO、纯时间;DATE 取日期部分、DATE_TIME 纯日期补 00:00、TIME 从 datetime 取时间;严格解析;文本 Excel 序列号仅在 DATE/DATE_TIME 且 1..2958465 兜底;10/13 位时间戳仅在表头/字段名含“时间戳/timestamp”且范围合理时解析。
  • MONEY → BigDecimal:全角转半角;去 ¥¥$€£元/空格;支持正负号、括号负数;千分位校验后去除;支持科学计数法;百分号默认拒绝;setScale(2, HALF_UP);整数位 >16 按坏值处理。
  • NUMBER → BigDecimal:千分位/科学计数法;按 DECIMAL(38,6) 校验精度。
  • PHONE/ACCT_CARD_NO/ID_CARD_NUMBER:电话支持 +86/空格/横线;卡号拒绝科学计数法、校验 13–19 位(可选 Luhn);身份证支持 15/18 位并校验出生日期/校验位;失败返回 null 而非空串。
  • fieldType 统一 trim+uppercase;类型化列使用保留 +/-/. 的清洗规则,TEXT 保持既有行为。
  • 初始化 GlobalCache.FUN_REGULAR_MAP 五个具名正则(EXTRACT_NAMED 目前空实现);未知 key fail-open 并记日志。

A2. 类型来源与 ColStat

  • 优先级:出口 A table_field → AI 模板 ai_clean_field → ColStat.shape → 表头关键词 → TEXT。
  • AiRulePlan.forAi 接收 List<ColStat> 与候选模板:绑定列用模板类型/required/fieldLen/directionConf;未绑定列用形状;自建表全部用形状+关键词。
  • forStoredTemplate 填 directionConf;forExisting 补 zf/directionConf;新增 forExistingColumns 供 verify。
  • typeOf 改词边界/中文关键词,补 TIME/NUMBER,修 card_no/id_card/cert_no/accountNo,避免 updateUser/candidate/totalTime 误判。
  • 修 ColStatBuilder.shape:日期时间/纯时间、负/括号/币种金额、13–19 位卡号不判 NUMBER、带前缀手机号;日期规则用 find/锚定正则。
  • 自建表按形状建 DATE/TIME/TIMESTAMP/DECIMAL 列,不再全 TEXT。

A3. DDL 与数值范围

  • DuckTypeMapper:MONEY → DECIMAL(18,2)(保持兼容,入库校验 16 位整数);NUMBER → DECIMAL(38,6);显式列出 PHONE/ACCT/ID/DEBIT/CALL → TEXT;AI 表不使用 VARCHAR(n),避免超长整批失败。
  • FunMultiplier 用 BigDecimal.multiply 重写;SCALE 整数/小数一致。
  • 入库前校验 DECIMAL 精度/长度,超范围按坏值处理,不让 DuckDB 抛到批处理层。

A4. 校验与置信度

  • verify 使用与 load 相同的执行计划(出口 A forExistingColumns、复用 forStoredTemplate、新建 forAi);抽 AiRulePlan.resolve(...) 一处生成。
  • requiredFillRate 改为逐行 required 非空率;llmScore 传 draft.confidence。
  • 任何 required 列被抽样拒绝(错误率 >5%)→ 直接 NeedReview,不静默删列。
  • 复用 AI 模板也跑一次抽样 verify(raw 表 300 行,无模型调用);精确 md5 直通只跳过模型,不跳过类型/必填检查。

A5. 失败隔离与清理

  • 新表:异常/零行/注册失败 → drop 新表,不登记。
  • 复用表:先写新 job 的行,成功后删除同 file_id+sheet_id 且 ai_job_id != 当前 的旧行;失败时 deleteByJob 清理半截行,旧行保留。
  • 复用表不存在 → 按存档字段在当前案件 createAiTable;失败则 FAIL,不跨案件写。
  • 拒绝记录按 job 清理;删除文件/撤销任务同步清理 ai_clean_reject。

A6. 数据库变更

  • ai_clean_job:row_rejected、row_warned、row_unprocessed;ai_clean_batch:loaded_rows、rejected_rows、self_built_count。
  • 案件 DuckDB:懒建 ai_clean_reject。
  • 更新 sql/ai_clean.sql + 幂等迁移脚本/启动迁移(ALTER TABLE ... ADD COLUMN IF NOT EXISTS)。

A7. 测试

  • 格式矩阵:日期/时间/金额/数字/电话/身份证/卡号;必填失败整行拒绝、非必填置空计数。
  • 行级诊断:10 行样本中 3 行必填坏值 + 1 行非必填坏值 → row_total=10, row_loaded=7, row_rejected=3, row_warned=1;ai_clean_reject 3 条,row_no/列名/reason/errors_json 正确;API 分页可查。
  • 一行多列错误 → 记录全部列错误,blocking 列取第一个。
  • 函数异常(SCALE/EXTRACT)必填 → 行拒绝并记 REQUIRED_FUNC_ERROR。
  • 真 DuckDB:DATE/TIME/TIMESTAMP/DECIMAL 混合格式全量入库;坏值不导致整批失败。
  • 复用失败保留旧行;新表失败清理;写入异常统计 row_unprocessed。
  • 统计:SUCCESS/NEED_REVIEW/SKIP/FAIL/自建表的 batch、SSE、getFiles 数字一致。
  • 脱敏:身份证/手机号/卡号在日志和接口中掩码。

三、Commit 2(Phase B):模板匹配提升

B1. 表头规范化与指纹

  • 括号内容保留为语义 token(金额(元)→金额元、账号(本方)→账号本方);只去装饰性标点/空白。
  • 指纹只取非空表头并编码位置(0=姓名|2=金额),尾部空列/padding 不影响;headers 改 JSON 存储(legacy 逗号 fallback)。

B2. 复用安全与案件隔离

  • AI 模板复用必须 headerMd5 完全相等且列序一致;否则按表头名重映射或重调模型,绝不直接套旧 colIndex。
  • 复用也跑 verify;复用前确认当前案件物理表存在,缺失则按存档字段建表。
  • 唯一键改 (case_id_src, header_md5);同批次同表头按 (case, md5) 复用;register 冲突后复用/重激活 OFFLINE。
  • 列数不一致不再 dropTable:ALTER TABLE ADD COLUMN 或新模板;只删当前 file/sheet 的行。

B3. 打分与出口闸门

  • 增加字段名匹配、required 覆盖、ColStat.shape 类型兼容、表头顺序;修正 Jaccard 去重。
  • 同义词小词典(户名/账户名称、发生额/交易金额、交易日期/日期、账号/卡号、电话/手机号等)。
  • 阈值自适应:短模板 ≤3 列要求全中或 ≥0.9;普通模板 STRONG≈0.85 + required 全覆盖 + shape 不矛盾;WEAK≈0.55 接入。
  • 出口 A 必须确定性分数达标 + required 覆盖;模型自评只做辅助,0 分候选不能靠 0.6 自评升 A。

B4. 提示词与绑定校验

  • 候选 brief 增加 score/来源/category/表名/required/类型/字段中文名;AI 模板展示 cN + 中文名 + 类型。
  • 输出改稳定 fieldId + fileColIndex(明确 0 基);filterBinds 做 trim/大小写/下划线归一,接受中文/英文别名,检测 1-based 偏移、重复源列/目标字段。
  • 无效绑定回灌模型 repair 一次;仍失败 → NeedReview;第二次 askLlm 单候选重编号 #1。
  • 模型 headerRow 校验后使用;headWindow 改为围绕 primaryBlock 表头行。

B5. 向量召回与候选池

  • mergeRecall 真正补候选:召回命中不在池内的 ONLINE 模板加载字段后追加;去重;header top N + vector top M 配额。
  • 嵌入/查询文本同构;移除随机 ai_clean_{id}、通用 sheet 名、纯 cN;加入分类/字段中文名/类型。
  • 候选池一次 join + 缓存;跳过无字段模板;按表头/字段集合去重;过滤 main_id 不在 StrategyType 的既有模板。

B6. 探测与结构

  • CSV 走 AiRowReader/raw 表流式前 200 行探测,不再用 POI;CSV 不再 Skip。
  • 加密文件把 FileInfo.password 传入 SheetProbe/AiRowReader。
  • 多块:按 BlockHint 每块建 job(block_no 已有唯一键),每块独立 headers/colStats/md5;入库只读 firstDataRow..lastDataRow。
  • 合并单元格:先尝试表头压平(横向填充、纵向 / 合并),成功则不转人工;数据区合并不再一刀切;无法压平才 NeedReview;BlockHint.mergedRegions 真正装配。
  • 表头定位加入列形状/文本密度/关键词/下一行数据打分;结构门移到模型调用之前。

B7. 指标与闭环

  • 批次增加漏斗:headerCache/scoreHit/llmHit/selfBuilt/needReview/failed;matched_by 区分 HEADER_CACHE/SCORE/LLM/SELF_HEADER。
  • 后续:人工修正 API、DRAFT→ONLINE 晋升、负样本/golden set。

B8. 测试

  • 归一化/指纹:括号限定词、单位、尾部空列、列序、逗号列名。
  • 匹配:同义词、短模板、列序变化、低分候选不得出口 A、向量补候选、配额。
  • 复用:精确 md5、列序变化重映射、表不存在建表、同批次同 md5、跨案件隔离。
  • 探测:CSV、加密、多块、合并表头、20 行以后表头。
  • 端到端:多块/合并/CSV 实际入库;匹配漏斗计数;无错列写入。

四、验收标准

  • Commit 1:上述行级诊断/计数测试全绿;真 DuckDB 上 DATE/TIME/TIMESTAMP/DECIMAL 不再整批失败;ai_clean_reject 能查到每一行被拒的列与原因;row_total=row_loaded+row_rejected(成功任务);未清洗 sheet 不再计成功。
  • Commit 2:golden set(同表头/同义词/列序/多列/少列/括号单位)档位符合预期;列序变化不写错列;同批次同 md5 全部入库;跨案件互不污染;CSV/多块/合并表头可入库。
  • 前后对比:用 ../../docs/design/smart-clean-test-cases.md + 三个真实样本跑同一批,记录匹配漏斗、成功/失败行数、逐列错误率;以“自建表不参与匹配率、未清洗不计成功”后的真实基线为准。

五、风险与顺序

  • 拒绝记录表会随数据量增长:按 job/file 清理 + 可配置保留上限;应用日志只采样。
  • 脱敏默认开启,避免身份证/手机号/卡号进日志。
  • 必填严格化会增加失败行,但每一条都能在 ai_clean_reject 中看到原因。
  • 唯一键变更需要迁移/回滚脚本;先建新索引、回填 case_id_src、再删旧索引。
  • 多块/合并表头会增加 job 与模型调用;加每 sheet 最大块数、每批 token 预算护栏。
  • 先提交 Commit 1 并跑完测试,再开始 Commit 2。