1. 24 Aug, 2026 15 commits
    • Data Governance Dev's avatar
      fix(web3): ExplainIssueDialog match(一致)时不展示一致性结论条 · 323090fd
      Data Governance Dev authored
      LLM 审查结论为 match(代码正确实现规则)时,右栏的「✅ 一致:代码正确
      实现了规则」绿条不再展示 —— 既然一致,这条结论本身就成了冗余信息。
      违规原因卡片继续展示,左栏的规则说明 + 代码也始终保留,方便用户对照。
      323090fd
    • Data Governance Dev's avatar
      feat(web3): 字段配置 + 任务字段表加 ⤒ 置顶 + 重试提示跟随滚动 · a3b54910
      Data Governance Dev authored
      字段配置(FieldConfigDrawer)与任务编辑(TaskModal 默认展示列):
      - ↑↓ 按钮组前再加一个「⤒ 置顶」按钮,splice(idx, 1) + unshift
        把对象字段直接搬到列表最前(非交换),首行禁用
      - FieldConfigDrawer 三个按钮右对齐:把 .field-comment-icon 的
        margin-left: auto 移除,让 .ord-group 独占推右责任,ⓘ icon 紧跟其后
      
      ExplainIssueDialog LLM 弹框:
      - LLM 未按 JSON 输出时的重试说明(原独立 .note-block 灰底框)
        嵌进违规原因卡片底部,跟 reason-text 一起被父容器滚动
        - 虚线分隔 + 小字号 + 灰字,跟违规原因区分但不抢眼
      a3b54910
    • Data Governance Dev's avatar
      feat(web3): 字段配置加 ↑↓ 调整展示顺序 · cfc4c803
      Data Governance Dev authored
      字段配置(FieldConfigDrawer + TaskModal 字段表)新增 ↑↓ 排序按钮,
      调整字段在结果表里的展示列顺序:
      
      - FieldConfigDrawer:每项右侧加 ↑↓ 按钮组,icon 左边
        - 把原来的 localChecked dict 改成 localItems 数组(同时管顺序 + 勾选)
        - 保存时 emit 完整字段顺序 + 仅勾选字段
      - TaskModal:字段表去掉最左「排序」列,只在「默认展示」checkbox
        左边留 ↑↓(统一入口,列宽 130 → 170px 给按钮腾空间)
        - 空数据行 colspan 同步 6 → 5
      - ResultTable:allColumns computed 在 visibleFields 非空时按其顺序排
        (FieldConfigDrawer ↑↓ 调整的结果),无 visibleFields 时回退原序
      - 样式:.ord-btn / .ord-cell / .ord-group 抽到 global.css
        初始上下排版,用户反馈丑 → 改为左右排版(flex-direction: row)
      cfc4c803
    • Data Governance Dev's avatar
      fix(web3): ExplainIssueDialog 规则说明过长导致左栏溢出截断 · 8450d6f3
      Data Governance Dev authored
      现象
      点「!」弹框左栏「规则说明」desc 很长的规则(如邮箱校验规则 8 / 200+ 字),
      整段 desc 没有最大高度限制,把左栏撑爆,下面的「规则代码」标题 + code-block
      被视觉截断;右侧的 pane-title-tag(规则类型胶囊)跟 desc-block 视觉重叠,UI 崩。
      
      修复
      src/components/ExplainIssueDialog.vue
      - .explain-dialog 加 min-height: 0
        (flex 子容器需要 min-height: 0 才能让 overflow 真正生效;
         否则 flex item 默认 min-height: auto,会按内容撑开无视 max-height)
      - .desc-block 加 max-height: 200px; overflow-y: auto
        跟 .code-block 视觉统一;超长 desc 内部独立滚动,不再撑爆左栏
      
      效果
      - 规则说明超 200px → 内部独立滚动条
      - 整个 dialog 高度超 84vh → .modal-body 兜底滚动
      - 两层滚动不冲突(外层兜底,内层是单段 desc 限高)
      
      验证
      npx vite build → ✓ built in 6.80s
      
      注
      4 个 web3/tests/*.png modified 未提交(与本修复无关)
      8450d6f3
    • Data Governance Dev's avatar
      feat(web3): 规则设置弹窗 · 从其他任务导入规则 + 字段中文标签 · 5f6fe045
      Data Governance Dev authored
      动机
      1) 每建一个新任务都要从零手写规则,重复劳动严重。
         任务间的规则往往通用(手机号格式、身份证校验、年龄范围),希望能从已有任务复制。
      2) 导入弹框只显示 ZJHM 这种英文列名,业务人员看不懂字段是啥意思。
         需要在字段标题后补字段中文标签(数据库列注释)。
      
      改动
      src/components/ImportRulesDialog.vue (新)
      - 左右两栏布局(960px 宽):
        · 左列:所有非自身的任务列表(listTasks(),过滤掉 currentTaskId)
        · 右列:选中任务的所有字段+规则(getTask(id) 拿详情,按字段分组),每条规则 checkbox
      - 字段标题:field_key + field_comment 横排显示(comment 为空时「(无字段注释)」灰斜体兜底)
      - 顶部「全选 / 反选」;底部「已选 N 条 + 取消 + 导入 N 条」
      
      src/components/RuleModal.vue
      - 在 + 添加规则 下方加「📥 从其他任务导入规则」按钮(绿色边区分)
      - 新增 taskId prop + importOpen ref + onImportRules()
      - 去掉后端 id(避免保存时按 id 当 update 走错路径),用 Date.now()+count 生成临时占位 id
      - 清空 testVal/testResult(让用户在新字段下重新测试)
      
      src/components/TaskModal.vue
      - <RuleModal> 加 :task-id="props.task?.id || 0"
      
      backend/routers/tasks.py
      - FieldOut 加 comment: Optional[str] = None
      - 新增 _fetch_field_comments(t, conn):用任务自身的 conn_json 连一次数据源,
        调 db_adapter.list_columns() 拿 {field_key: column_comment}
        · 连接信息不全 / 连不上 / 表不存在 → 降级返回空 dict,不报错(comment 是辅助信息)
        · 复用既有 db_adapter,无新依赖;schema/table_name 走绑定参数无 SQL 注入
      - _row_to_out(include_field_list=True) 时拉一次 comment_map 注入每个 FieldOut
      
      关键决策
      - 后端主动拉 comment 而非前端:前端拿不到密码
      - 失败不报错:comment 缺失不影响主流程
      - 不持久化到 Field 表:跟 plan §4 决策一致——数据字典不存,每次按 task 重新拉
      
      Bug 修复
      第一次实现时 computed 返 {fieldKey, fieldComment, rules},但模板里写了 v-if="group.comment",
      comment 没定义 → 永远 falsy → 显示「(无字段注释」兜底。不报错只静默渲染空。
      改成 v-if="group.fieldComment"。已记到 memory(vue-computed-template-prop-name-mismatch)。
      
      验证
      - 后端 TestClient: GET /api/tasks/24 → field_list[ZJHM].comment = '证件号码 | 数据标准: 01010007' ✓
      - npx vite build → ✓ built in 7.09s(1640 modules,无报错)
      
      注
      - 4 个 web3/tests/*.png modified 未提交(与本功能无关)
      - 后端有改动需用户重启进程
      5f6fe045
    • Data Governance Dev's avatar
      feat(web3): 数据明细「!」一键问 LLM · 违规原因 + 规则 vs 代码一致性审查 + 多 tab 并行 · 5cb07f5a
      Data Governance Dev authored
      动机
      用户在结果表看到红 ! 标记的不合规行时,只能从 tooltip 看违反哪条规则,
      但不知道为什么错、这条规则的代码是否真的实现了规则描述。需要点 !
      直接问 LLM,让模型同时审计数据违规原因和 规则 vs 代码 一致性。
      同一字段常违反多条规则(如「11 位数字」+「号段分配」),需要开多个 tab 并行诊断。
      
      改动
      backend
      - core/ai_explain.py (新): explain_issue() 拼 prompt 调 LLM,解析 JSON,
        失败时附带上次原文 + 修复提示自动重试,最多 3 次;JSON 解析失败不抛异常,
        降级返回原文 + consistency='unknown'
      - routers/ai.py: 新增 POST /api/ai/explain_issue 端点
        (ExplainIssueRequest/ExplainIssueResponse Pydantic)
      - routers/queries.py: _evaluate_row() 的 issues 字典追加 rule_id
      - core/session_manager.py: CompiledFieldRules.rules 快照元组 6 元→7 元
        (追加 rule_id),让 _evaluate_row 能拿到规则 id
      
      frontend
      - components/ExplainIssueDialog.vue (新): Teleport + 自定义 .modal 模式
        · ctx-strip 顶部展示字段名 + 原始值(所有 tab 共享)
        · 多 tab 行:每条违规规则一个 tab,打开即 Promise.allSettled 并行调 LLM
        · 每个 tab 独立 loading/result/error 状态(互不干扰)
        · tab 标题旁小圆点:加载中蓝色脉冲 / 失败红 ⚠ /
          完成按 consistency 染色(绿✓ / 红! / 黄? / 灰·)
        · 单 issue 时隐藏 tab 行
        · 「重新分析」只重跑当前 active tab
      - components/ResultTable.vue: .err-flag 加 cursor:pointer + @click.stop,
        emitExplainIssue() 把该字段所有命中的 issue 都 enrich 后塞进 issues[]
        一起 emit(之前只取第一条,第二条违规原因被忽略)
      - views/DataQualityView.vue: 监听 explain-issue → 打开 ExplainIssueDialog
      - api/ai.js: explainIssue(payload) 导出
      
      LLM Prompt 关键设计
      - 系统提示强调「先想清楚再用严格 JSON 输出」
      - 用户提示拆任务 1(违规原因)+ 任务 2(一致性审查),
        加 ASCII 双引号避坑(历史踩坑:value 里用裸 " 会让 json.loads 中断)
      - 一致性三档:match / code_bug / rule_ambiguous
      
      关键决策
      - Promise.allSettled 而非 Promise.all:单条 LLM 失败不应拖垮其他 tab
      - LLMUnavailable 不重试(无 key / 网络挂 / 限流):立即降级返回
      - 重试时附上次原文:让 LLM 看到自己刚才错在哪,比纯说「修一下 JSON」有效
      - 「重新分析当前规则」而非「全部重试」:定位精准,省 token
      
      E2E 验证
      - regex 类型:consistency='code_bug'(LLM 正确识别硬性限制年份的 bug)
      - number 类型:consistency='rule_ambiguous'(LLM 指出「0-150 是否合理」需业务再斟酌)
      - npx vite build → ✓ built in 10.92s(1638 modules transformed,无报错)
      
      注
      - work-logs/2026-08-24.md 已记录三轮迭代详情 + 踩坑
      - 另有 4 个 web3/tests/*.png modified 未提交(与本功能无关)
      5cb07f5a
    • Data Governance Dev's avatar
      fix(web3): in-flight 时禁止切换分组 · f3a3ab93
      Data Governance Dev authored
      用户反馈:邮箱分组跑完后切别的分组能看到老结果 → 上一节已修;
      再继续叠加"in-flight 期间切分组→ 切回来自动续跑"方案后又出新 bug
      (resultFields 与 rows/lastResult 串数据),放弃了复杂的续跑逻辑。
      
      简化方案:watch(type) 头部拦截 runningQuery=true,
      弹 ElMessage.warning + router.replace 回原 type,不做切。
      
      - typeReverting 旗子防 router.replace 触发的二次 watch 循环
      - 跑完后的 cacheByType / applySnapshot 保留(切走再切回仍能看历史结果)
      - continueQuery + takeSnapshot.inFlight 元数据暂留(未来如恢复续跑逻辑可复用)
      - build: ✓ built in 6.81s
      f3a3ab93
    • Data Governance Dev's avatar
      fix(web3): TaskModal 编辑任务时 schema/table 被 watch(form.dbType) microtask 阶段清空 · 25cb466d
      Data Governance Dev authored
      现象:编辑 Oracle 任务,「数据连接」Tab 的 Schema/数据表 下拉空白显示
      「请先测试连接」。连接字段正确回填,但 schema/table 区域像新建任务一样
      要重新选。
      
      根因:watch(open) 编辑分支同步执行 form.dbType = task.db_type(即把
      默认值 'MySQL' 改成 'Oracle')后,Vue 把 watch(form.dbType) 排进 microtask;
      同步代码继续把 form.schema / form.table 从 task 回填完毕。等当前 microtask
      跑完,watch(form.dbType) 才执行函数体,里面 '切方言就清 schema/table' 的
      清理逻辑把刚回填的值一并清空。MySQL 任务也有这个 bug,只是 schema 通常
      等于 db,看起来症状没那么明显。
      
      修法(方案 A):
      - 加 isInitializing ref 作为初始化旗子
      - watch(open) 入口 isInitializing=true,结束后 await nextTick() × 2 + false
      - watch(form.dbType) 头部 if (isInitializing.value) return
      
      用户主动切 dbType / applyPreset() 行为不变(旗子默认 false,watcher 正常清理)。
      
      验证:
      - npx vite build → ✓ built in 7.00s
      - 编辑 Oracle 任务 → Schema/数据表 自动回填,不再显示「请先测试连接」
      - 新建任务切 dbType / applyPreset() 行为不变
      25cb466d
    • Data Governance Dev's avatar
      feat(skill): 新增 find-field —— 跨库盘点字段 + 行数 + 视图溯源 · 0634fb08
      Data Governance Dev authored
      新建 .claude/skills/find-field/:
      - SKILL.md:调用方式 / 参数 / 输出格式 / 6 条踩坑 / 安全约束
      - scripts/find_field.py:从 web3 connection_preset 读连接,三方言
        (MySQL/达梦/Oracle) 扫列名 + COUNT(*) + USER_DEPENDENCIES 溯源 V_* 视图
      
      安全:
      - 数据库连接只能选 web3 已存的(13 个预设),不允许临时输入密码
      - 只读 SELECT,不发 DML(CLAUDE.md 原则)
      
      附带 web3/tests/_probe_gjj80_bankcard.py:
      GJJ80 一次性探测脚本,作为本次 skill 命名的原型被保留作历史对照。
      SKILL.md 里点名「已被本 skill 取代」,但保留 git 历史。
      
      验证:find-field huanggang-gjj 银行卡号 → 与一次性脚本结果完全一致
      (V_JCFX_DWXX 11768 + V_JCFX_GRXX 567523 + 基表 GJ_DWXX/GJ_GRXX)
      0634fb08
    • Data Governance Dev's avatar
      feat(web3): RuleModal 新建规则时「跳过空值」默认勾选 · b0b09f46
      Data Governance Dev authored
      - src/components/RuleModal.vue:248 addRule() 把 skip_null 默认值 false→true
      - 编辑/复制老规则不走 addRule(),skip_null 仍按 r.skip_null 实际值显示
      - 保存链路 onSave 仍透传 !!rule.skip_null,不强制覆盖存量数据
      
      业务上「空值不计入不合规」更符合直觉,省得用户每加一条新规则手动勾。
      b0b09f46
    • Data Governance Dev's avatar
      feat(web3): 导出 Excel 加违规单元格浅红 + Review Note 写规则详细 · 8d71b57f
      Data Governance Dev authored
      页面上数据明细的红格 + hover tooltip 在 Excel 里丢了,
      业务人员拿到的导出文件看不到「哪几格不合规 / 为什么不合规」,
      还得回系统查。
      
      src/utils/excel.js 加 applyErrorHighlight():
      - 数据行起始偏移 = 2(前两行是注释行 + 字段名行)
      - row.errorCells.includes(cols[C].key) → 给那个 cell 加浅红底 + 写 Excel Note
      - 浅红 #FEF0F0 + 红字 #F56C6C,跟页面上 .cell-error 同色
      - Note 内容每行一条 issue:规则名:desc(name 空时回退 desc)
        —— 跟 ResultTable.errTip 同款格式;Note 空间够,把 desc 详细全展开
      - 作者标「数据治理工具」方便业务识别
      - 调用方零改动:errorCells/issues 已在 DataQualityView 透传,excel.js 直接读
      - 不传 errorCells/issues 的复用场景安全:length===0 直接 continue 跳过
      8d71b57f
    • Data Governance Dev's avatar
      fix(web3): ResultTable cell tooltip 精简只列规则名 · a9e0336b
      Data Governance Dev authored
      errTip() 改成 .filter() + join(';') 后,格式是 name(desc),
      但 desc 太长(动辄一二百字),在小 tooltip 里展开反而看不清是哪几条规则。
      
      cell tooltip 只列违规的规则名(name),desc 不再展开;
      表头 ruleHint 保持 name(desc)不变,那里用户需要看规则定义。
      
      效果对比:
      - 改前:11位数字(11位数字);首位两位数字规则(第 1 位:固定为 1 ...)
      - 改后:11位数字;首位两位数字规则
      
      老规则(name 为空串)回退到 desc。
      a9e0336b
    • Data Governance Dev's avatar
      fix(web3): RuleModal 允许保存空规则列表 · 9890df70
      Data Governance Dev authored
      字段原本有规则,点「规则设置」进 RuleModal 把所有规则删完,
      「保存」按钮被 length>0 限制变灰卡死。
      
      语义上用户明确点删除就是想清空,字段应该回到「无规则」状态;
      后端 _replace_fields 收到空 f.rules 也只是不写任何 Rule,本来就支持。
      
      去掉 length>0 && 这一截。Array.every() 对空数组天然返回 true
      (vacuous truth),删空所有规则后保存按钮就亮起来。
      9890df70
    • Data Governance Dev's avatar
      feat(web3): rule 表加「规则名称」列 → 前端展示「违反哪条规则」 · 69eea339
      Data Governance Dev authored
      数据明细(不合规行)当前只展示「哪个字段不合规」,没有「哪条规则被违反」。
      desc 是给 LLM 看的自然语言(含「必须 18 位」指令式描述),
      不适合直接给用户看。加独立字段 name 给前端展示,desc 继续喂 LLM。
      
      【schema 变更】
      - rule.name TEXT NOT NULL DEFAULT ''(老规则保持空串,迁移不破坏存量)
      - _migrate_rule_name():ALTER TABLE ADD COLUMN name(幂等,123 条老规则验证保留)
      
      【API 校验】
      - RulePayload.name: str = PydField(..., min_length=1) —— 必填
      - RuleOut.name: str = "" —— 老规则空串兜底
      
      【检测引擎链路】
      - CompiledFieldRules.rules 5 元 → 6 元(追加 name)
      - _load_task_compiled snapshot / rule_list 都带 name
      - _evaluate_row 返回 issues: {field, name, desc}
        → bad_rows 透传到前端「数据明细」备注列
      
      【前端 UI】
      - RuleModal:在「规则说明」上方加「规则名称」输入框(红星必填)
        - hasValidRules 加 name.trim() 校验
        - onSave 序列化带 name
        - namePlaceholder 按 rule_type 给不同示例
        - scoped .required 红星样式(不污染全局)
      - ResultTable:ruleHint / errTip / 备注列 issues 优先展示 name
      - FieldConfigDrawer:ruleHint 同步升级「name(desc)」格式
      
      【兼容策略】
      - 老规则 name=空串,UI 三处自动回退 desc 显示
      - 编辑老规则保存时强制补 name(避免历史脏数据持续累积)
      
      【测试】
      - test_rule_types_e2e 6 处 rule payload 补 name(Pydantic 必填后端会拒 422)
      - pytest tests/ → 123 passed
      - vite build → ✓ built in 9.64s
      - 冒烟:init_db() 对线上 DB 跑迁移,老规则全保留默认 ''
      69eea339
    • Data Governance Dev's avatar
      fix(web3): TaskModal「测试连接」按钮被长提示挤成竖排 · cc4144fd
      Data Governance Dev authored
      数据连接 Tab 点测试后,提示文本把按钮挤到 ~30px,
      「测试连接」四字竖排(测/试/连/接 各一行)。
      
      根因:
      - .conn-form-grid 给所有 input 行加了 max-width:150px,
        但 selector 没排除 .form-row.full,「测试连接」行
        是 .full(横跨 3 列 = 720px),里面的 .form-control
        却被锁死 150px
      - 通用 .btn 没有 flex-shrink:0,被 flex 父挤压
      
      修两处:
      - .conn-form-grid > .form-row:not(.full) .form-control
        限宽 selector 收窄到非 full 行,6 个 input 行照旧,
        full 行放开用满 720px
      - 通用 .btn 加 flex-shrink:0 + white-space:nowrap,
        顺手防御其他按钮同类问题
      cc4144fd
  2. 21 Aug, 2026 25 commits
    • Data Governance Dev's avatar
      feat(web3): FieldConfigDrawer 字段后也显示「⚠ N规则」徽章 · e4af262d
      Data Governance Dev authored
      结果表表头已经显示规则数徽章 + tooltip 列规则 desc,但字段配置面板(drawer)
      只显示字段名 + 注释图标 —— 用户看不到「这个字段有几条规则」。
      
      复用 f.rules / f.ruleList(DataQualityView 在 resultFields 已经塞了,
      不用改父组件),加 el-tooltip + ⚠ N规则 徽章。
      
      布局:rule-mark 插在 .en 后、comment icon 前;
      .comment-icon { margin-left: auto } 推最右,所以
      - 有规则 + 有 comment:cn/en/rule-mark 靠左,icon 推右
      - 有规则 + 无 comment:cn/en/rule-mark 自然铺开
      - 无规则 + 有 comment:cn/en + icon 推右(不变)
      - 无规则 + 无 comment:cn/en(不变)
      
      scoped CSS .rule-mark:黄橙色 #e6a23c + help 光标,
      跟 ResultTable 表头 rule-mark 视觉一致。
      e4af262d
    • Data Governance Dev's avatar
      feat(web3): 沙箱 import 白名单扩到常用纯计算类库(json/unicodedata/collections/dataclass 等) · c0bdbc92
      Data Governance Dev authored
      用户反馈:AI 生成 JSON 校验函数,运行时报 ImportError: 'json' is not allowed。
      原白名单只有 6 个模块(datetime/time/math/decimal/re/_strptime),太窄。
      
      设计准则:「只能改内存里的 Python 对象,不能碰进程外的东西」——
      纯计算 / 数据结构类库一律放;I/O、网络、进程、动态执行类一律不放。
      
      白名单(31 个):
        数据 / 时间  : datetime, time
        数学 / 数值  : math, decimal, fractions, statistics, numbers, operator
        字符串 / 编码: re, string, unicodedata, html
        数据结构     : collections, collections.abc, itertools, functools
        序列化 / 二进制: json, csv, struct, array
        编码 / 哈希  : hashlib, base64, binascii
        实用工具     : uuid, enum, dataclasses, typing, difflib
        其他         : copy, pprint, reprlib, ipaddress
      
      故意不放:os/sys/pathlib/subprocess/pickle/ctypes/importlib/socket/...
      连 urllib.parse 也不放(import urllib 后 urllib.request 属性访问能拿到,副作用太大)。
      
      沙箱三件套(踩坑):
      1. builtin 白名单加 __build_class__ —— class X: / enum.Enum / @dataclasses.dataclass 都靠它;
         动态 type('X', (), {}) 走 type.__call__ 不经过它,所以风险 ≈ 0
      2. namespace 预填 __name__ / __qualname__ —— enum/dataclass metaclass 会查
      3. __name__ 必须用 '__main__' 不能用假名 —— dataclasses._is_type 内部
         sys.modules.get(cls.__module__).__dict__,假名返 None 然后 .__dict__ 报错
      
      另:
      - ai_rule.py 的 number / date / string prompt 都加上「沙箱已预注入的常用模块」清单
      - RuleModal.vue 的 string rule 提示区也列已预注入模块清单
      - test_rule_runner.py 新增 TestSandboxImports 类,46 个用例
        (26 个模块 smoke + 3 个 import 形式 + 16 个危险模块被拦 + 1 个 urllib 被拦)
      
      验证:
        python -m pytest web3/tests/  →  123 passed, 3 warnings
        npx vite build                →  ✓ built in 6.84s
      c0bdbc92
    • Data Governance Dev's avatar
      fix(web3): TaskModal UI 整理(基础信息两列对齐 + 连接字段3列紧凑布局 + 删冗余提示) · 75f471e3
      Data Governance Dev authored
      基础信息 tab:
      - 删除「用户在查询下拉框看到的名称,建议格式:校验类型_表名」提示文字
      - form-grid 加 .basic-info-grid class + CSS 让前两个 form-row(任务名称 / 任务分组)
        各占 grid 一列水平对齐;任务说明仍 .full 占整行
      
      数据连接 tab 「连接方式」section:
      - form-grid 加 .conn-form-grid class + CSS 改 3 列布局 + 固定列宽 240px
      - .form-control { max-width: 150px } 缩 input 容器 —— 只在 conn-form-grid 作用域下生效,
        不污染全局 .text-input width:100%(避免影响其他弹框 FieldConfigDrawer / RuleModal 等)
      - 6 个 input 紧凑靠左排列,不再撑满 modal
      
      数据连接 tab 「数据表」section:
      - 删除「选择数据表后自动加载该表全部字段」提示文字
      - loading 提示「正在加载表列表...」保留
      75f471e3
    • Data Governance Dev's avatar
      feat(web3): 规则类型加 'string' + 「跳过空值」开关 · 33b60dc0
      Data Governance Dev authored
      字符串规则 (rule_type='string'):
      - RULE_TYPES 扩展;rule/code 列与 number/date 共用,存 Python def check(value) -> bool
      - ai_rule.py 加 _STRING prompt 模板(字符串语义:len/startswith/re)
      - RuleModal tab 加「字符串」+ desc/codePlaceholder/aiBtnLabel 分支 + Python 函数 hint 提示 value 是字符串
      
      跳过空值开关 (skip_null):
      - rule 表加 skip_null 列(INTEGER NOT NULL DEFAULT 0)+ 迁移函数
      - run_rule() 入口短路 None/空 str/空 bytes(regex 在 _evaluate_row 内联短路)
      - RulePayload/RuleOut + _replace/_copy/_row_to_out + _load_task_compiled 全链路透传
      - RuleModal 「规则类型」行右侧加 checkbox;测试/保存透传 skip_null
      - /api/rule/test 同步支持(用户在 RuleModal 测试空值时也走短路)
      
      UI 修复:
      - 「规则类型」行用嵌套 modifier .rule-type-row flex 化,不污染其他 grid cell 行
        (之前全局改 .rule-item-ctrl flex 把 .code-line/.test-line/.regex-line 全压塌)
      
      测试:
      - test_rule_types_e2e.py 加 4 个新用例(string 持久化 + dispatch + skip_null=False/True + regex skip_null)
      - FAKE_TABLE_ROWS 加 2 行空值('' + None),覆盖 skip_null 场景
      - test_rule_runner.py RULE_TYPES 常量断言更新
      
      后端 77 passed;vite build clean
      33b60dc0
    • Data Governance Dev's avatar
      feat(web3): 任务列表加「表注释」列 + TaskModal 选表时拉存 · 29987689
      Data Governance Dev authored
      1. TaskModal.vue
      
         form 加 tableComment 字段
      
         watch(form.table) 从 tableOptions 找对应项的 table_comment 写入
      
         编辑场景从 props.task.table_comment 回填
      
         payload 加 table_comment: form.tableComment || null
      
         选表时存一次到 task,列表直接读,零 N+1 查询
      
      2. TaskConfigView.vue
      
         模板加 <th>表注释</th>(位置在数据表后)
      
         colspan 8 → 9
      
         td 渲染 {{ row.table_comment || '—' }}
      
      3. 后端 commit 已加 task.table_comment 列 + 字段流通(commit 3)
      
         老任务没存过 → 列表显示「—」,重新编辑任务选一次表就有
      29987689
    • Data Governance Dev's avatar
      feat(web3): task 表加 checked_at + table_comment 元数据列 · 21ee05b2
      Data Governance Dev authored
      两个独立功能(同一个表的两列)一起做,因为迁移逻辑都走 init_db 兜底:
      
      1. task.checked_at: TIMESTAMP NULL
      
         schema.sql / Task ORM 加列
      
         _migrate_task_checked_at() 在 init_db 里兜底
      
         routers/queries.py::start_query 末尾:sm_put 后写 task.checked_at = utc_now
      
         try/except + rollback + warning:元数据写失败不能阻塞查询主流程
      
         时刻选 /start(不是最后一页 /page):中断也算「最近关注过」
      
      2. task.table_comment: TEXT NULL
      
         schema.sql / Task ORM 加列
      
         _migrate_task_table_comment() 兜底
      
         routers/tasks.py: TaskBase/Update/Out 加字段 + _row_to_out + create_task 写入
      
         update_task 的 setattr 循环自动覆盖
      
         list_tasks 接口直接读这列返前端,零查询开销
      
      3. CLAUDE.md「只做查询」边界
      
         这里写 task 元数据,不是业务数据修改
      
         业务表(field/rule/connection_preset)一律不 UPDATE
      
      4. 前端 TaskConfigView 现在用 row.table_comment 还没显示(commit 4 才加列)
      
         formatCheckedAt 已经备好(commit 2),checked_at 写完即可生效
      21ee05b2
    • Data Governance Dev's avatar
      fix(web3): 任务配置页分组选择没生效过滤 + checked_at 格式化 · 0c10c10a
      Data Governance Dev authored
      1. 分组过滤没生效
      
         <select v-model=groupFilter> 只绑了 model,没 watch 触发 load
      
         选了「手机号」不点「查询」list 不刷新
      
         加 watch(groupFilter, () => load()) 自动 reload
      
         keyword 保留「回车 + 查询按钮」(避免每个 keystroke 都请求)
      
      2. formatCheckedAt:把 ISO 字符串转成 'YYYY-MM-DD HH:mm'
      
         后端已返 checked_at(先 hardcoded None,下个 commit 接上)
      
         —— format 函数先备好,避免后续加 checked_at 字段后还要再改前端
      
      3. import 加 watch
      0c10c10a
    • Data Governance Dev's avatar
      feat(web3): ResultTable 列宽可拖拽(参照 web2 el-table resizable) · f33c4901
      Data Governance Dev authored
      web3 ResultTable 是自定义 table,el-table resizable 不能直接复用,自实现:
      
      1. ResultTable.vue
      
         columnWidths reactive + startResize/onResizeMove/endResize
      
         拖拽状态不放 reactive(频繁写不触发响应式刷新整张表格)
      
         window mousemove/mouseup(用户拖快会脱出 th 丢失 mouseup)
      
         拖拽过程 document.body user-select=none + cursor col-resize
      
         onBeforeUnmount 清理监听 + 还原 body 样式
      
      2. global.css
      
         .resize-handle 样式(透明 + hover 变蓝 409eff)
      
         table.dq-table 加 table-layout: fixed(auto 时 width 被无视)
      
         thead th 加 position: relative 让 handle absolute 定位
      
      3. 踩坑:改 Vue setup 的 import 段要核对所有 export 符号
      
         我改 import 时删了 watch 符号,但 setup 里 line 248 的 watch() 还在用
      
         → 运行时 ReferenceError: watch is not defined 把页面打白
      
      4. localStorage 列宽持久化没做(用户没要求;后续可加 key→width 存)
      f33c4901
    • Data Governance Dev's avatar
      feat(web3): 删导入/批量删除按钮 + 通用 Excel 导出(抽自 web2) · ef99984a
      Data Governance Dev authored
      1. DataQualityView 按钮区只留「字段配置 + 导出 Excel」
      
         删导入(btn-success)/批量删除(btn-danger)+ 对应的 mock 函数
      
         导出按钮加 :disabled(rows 为空或导出中禁用)
      
      2. 新增 src/utils/excel.js
      
         从 web2 IdCardTable.vue 抽 exportExcel + applyAllBorders + applyHeaderStyle
      
         封装成纯函数 exportToExcel({ rows, columns, filename, sheetName, confirm })
      
         三行表头:comment / cn / 数据;冻结前 2 行 + 黑色细边框 + 加粗灰背景
      
         columns 兼容 {key,cn,comment}[] 和 string[](fallback)
      
         大于 5000 行走 confirm(n) 二次确认(弹窗 UI 在调用方做)
      
      3. onExport 列范围
      
         visibleFields 非空 → 用户勾选;为空(未保存过)→ 退化为 resultFields 全量
      
         过滤掉 __reason(ResultTable 内部追加的备注列)
      
         文件名 <任务名>_<时间戳>.xlsx 防多次导出覆盖
      
      4. +xlsx-js-style 依赖(跟 web2 同库,导出样式一致)
      
         ⚠ bundle 多约 700KB(xlsx-js-style 是胖依赖,build 已超 500KB 阈值)
      
         后续可改 dynamic import(点导出按钮时再加载),本次没做
      ef99984a
    • Data Governance Dev's avatar
      feat(web3): 字段配置抽屉字段名旁加 ⓘ 图标 + hover 显示注释 · 270ccea9
      Data Governance Dev authored
      1. FieldConfigDrawer:cn 后面加 el-tooltip 包裹的 InfoFilled 图标
      
         仅 f.comment 非空时显示,没注释的字段不出 icon(避免噪声)
      
      2. global.css:.field-comment-icon 样式
      
         margin-left: auto 推到 flex 容器最右(关键,cn/en 用了 gap:8px)
      
         平时淡灰 #c0c4cc,整行 hover 时变蓝 #409eff + cursor: help
      270ccea9
    • Data Governance Dev's avatar
      feat(web3): 分页器改 el-pagination + 字段配置抽屉联动真字段 · eb5cd26b
      Data Governance Dev authored
      1. ResultTable:自定义分页器换成 el-pagination(带 ... 折叠中间页)
      
         pageSizes 默认 [10, 25, 50, 100, 200]、defaultPageSize=10
      
         加 visibleFields prop + allColumns 优先按它过滤(FieldConfigDrawer 联动)
      
         表格下方加 padding 让分页器不贴表格
      
      2. DataQualityView:字段配置抽屉改从 resultFields(后端 /start 返回的真字段)取
      
         visibleFields 默认按 show_default=true 重置勾选;用户手动保存后写入
      
      3. TDZ 修复:fields/visibleFields/watch 块挪到 resultFields ref 声明之后
      
         computed + watch immediate 在 setup 立即跑;const ref 在它前面会 TDZ 报错
      
         顺手清掉不用的 fieldsByType / violationRows import
      eb5cd26b
    • Data Governance Dev's avatar
      fix(web3): list_columns SQL 去重 ALL_COL_COMMENTS 防 UI 字段翻倍 · a4a064dd
      Data Governance Dev authored
      用户反馈:达梦 HGWXZJ 库的 YW_GJY(归集业务数据表)在「校验规则」Tab 每个字段
      都显示两遍(ID/ID、DWZH/DWZH、DWZJID/DWZJID、...),内容完全一致。
      
      根因推断:
        达梦的 ALL_COL_COMMENTS 里同一 (OWNER, TABLE_NAME, COLUMN_NAME) 偶尔有 2+ 行
        (历史 COMMENT ON COLUMN 重复 / DBA vs USER 视角双份 / 升级残留),
        LEFT JOIN 不去重 → ALL_TAB_COLUMNS 的每行匹配 2 行 → 整段 SQL 每列翻倍。
        web2 模板跟 web3 一致,理论上同样问题;web3 之前没碰到是因为 web2 没做过
        编辑场景自动 listColumns 的 UI 入口(手工流程绕开了)。
      
      改动(dameng + oracle 同源一起改):
        LEFT JOIN ALL_COL_COMMENTS 改为 ROW_NUMBER() 子查询预聚合,
        PARTITION BY (OWNER, TABLE_NAME, COLUMN_NAME) 每组取一条,
        ORDER BY COMMENTS DESC NULLS LAST(启发式:取最长注释当用户最后写的)。
      
      MySQL 模板不动(COLUMN_COMMENT 在 INFORMATION_SCHEMA.COLUMNS 自带,
      不在另一张表里,无 1:N 风险)。
      
      无单测(数据字典视图行为依达梦/Oracle 版本而异,fixture 不可靠);
      让用户在达梦 disql 跑 SELECT ... GROUP BY ... HAVING COUNT(*) > 1 验证 ALL_COL_COMMENTS 本身
      是否真有重复行。
      a4a064dd
    • Data Governance Dev's avatar
      fix(web3): resultFields.key 小写化对齐 db_adapter 列名归一 + errTip 直接用 key · b581e91a
      Data Governance Dev authored
      T4 任务(task_id=16,GJ_DWRYBD 表,39 字段全是大写 YWBH/FCRS/...)跑完查询,
      前端表格所有单元格都显示 (NULL),但备注列能正确显示「fcrs: 100到200之间」。
      
      根因(列名大小写不匹配):
        - 后端 db_adapter._rows_as_dicts 全局把列名归一为小写
          (normalize_column_name line 188)
        - row dict 的 key 全是小写(ywbh / fcrs / ...)
        - 后端 _evaluate_row 用 cf.field_key = f.field_key.lower() 访问,
          所以规则匹配正常(备注列能正确显示就是证据)
        - 但前端 resultFields.key 用了 f.field_key 原值(大写 FCRS)
        - ResultTable 里 row[col.key] → row['FCRS'] → undefined → (NULL)
      
      两处表现是同一个根因:
        - 单元格全 (NULL):row[col.key] 大写找不到
        - 错误 tooltip 永远 fallback「该字段不合法」:
          原版 errTip 用 col.cn(大写)找 row.issues[].field(小写),
          永远匹配不上
      
      改动:
        DataQualityView.vue resultFields 里:
          key: (f.field_key || '').toLowerCase()   // 跟 row dict 小写对齐
          cn: f.field_key                          // 表头显示保留原大小写
      
        ResultTable.vue errTip:
          直接用 col.key(小写)找 row.issues[].field(小写),
          去掉 col.cn 那一跳
      
      教训:「DB 列名大小写 × 字段名大小写 × 后端归一化策略」是反复出现的坑。
      db_adapter 全局归一为小写是契约,前端落 key 时必须主动 .toLowerCase()。
      b581e91a
    • Data Governance Dev's avatar
      fix(web3): 中断时只清进度,不清空已展示的不合规行 · cde42969
      Data Governance Dev authored
      用户反馈:T4 跑了几页后点「中断」,结果表里已经加载到的不合规行全没了。
      
      上一版 onCancel 调 resetQueryStatus(),它会把 rows / lastResult / resultFields / queryError
      全清空 —— 等于把用户已经看到的命中行擦掉。
      
      新增 clearProgressOnly():
        只清进度相关(sessionId / totalPages / totalRows / scannedPages /
        scannedRows / receivedPages),保留 rows / lastResult / resultFields
      
      onCancel 改用 clearProgressOnly:
        - 用户已看到的不合规行留住
        - 顶部 toast:「已中断(已扫 N 页,命中 K 条)」
        - 再次点「查询」会走完整 onQuery,自然用新结果覆盖
      
      教训:中断 ≠ 重查。reserve 用户可见状态是默认行为
      cde42969
    • Data Governance Dev's avatar
      feat(web3): 分页查询 orchestrator + 中断按钮 + 进度 + appendMode 行追加 · 8e389145
      Data Governance Dev authored
      把单次同步 runQuery 替换成分页 + 并发 worker 模式:
      
      DataQualityView.vue 重写查询逻辑:
        onQuery()
          1) POST /start → COUNT + 编译规则 + 拿 session_id + total_pages
          2) PAGE_CONCURRENCY=3 个 worker 并发抢 queue([1..total_pages]),
             每个 worker /page 拉到 → appendPage() 按页号升序拼进 rows
          3) 收尾聚合 lastResult + toast
      
        onCancel()
          - /cancel 摘 session(best-effort)
          - AbortController.abort() 让在飞的 fetch 立即 reject
          - resetQueryStatus() 清空 sessionId / rows / lastResult / queryError
      
        进度展示:task-info-bar 顶部「查询中 N/M 页 · 命中 K 条」
        onBeforeUnmount:cancel + abort,避免组件销毁后 fetch 还在飞
      
      ResultTable.vue 新增 appendMode prop:
        watch(() => props.rows.length, () => {
          if (!props.appendMode) currentPage.value = 1
        })
        默认 false 保持原行为(rows 替换 → 回第 1 页);
        append 模式下 rows 增量追加不重置页码
      
      ResultTable 新增 row-key 模式:父组件传 rowKey 函数,
      用后端注入的 __row_index 做稳定 key(append 模式下保证 Vue DOM 复用正确)
      
      PAGE_SIZE = 500(与后端默认对齐)
      
      无单测(前端无测试基础设施,手动 e2e 验证见 work-logs/2026-08-21.md)
      8e389145
    • Data Governance Dev's avatar
      feat(web3): 前端 API 接 AbortSignal + 三个分页端点 · 5fc62e5b
      Data Governance Dev authored
      client.js: request() 第三个参数 { signal } 透传给 fetch;
      http.{get,post,put,patch,delete} 都接受可选 signal。
      
      queries.js: 删 runQuery,新增三个:
        - startQuery(taskId, { pageSize, signal }) → /api/queries/start
        - fetchPage(sessionId, pageNo, { signal }) → /api/queries/page
        - cancelQuery(sessionId, { signal })    → /api/queries/cancel
      
      signal 是 AbortController.signal,配合 orchestrator 实现:
        1. fetch 主动 reject(节省带宽)
        2. 后端 /cancel 摘掉 session(后续 /page 立刻 404 短路)
      
      无单测(纯 plumbing,配合 commit 1 后端测试覆盖)。
      5fc62e5b
    • Data Governance Dev's avatar
      feat(web3): 查询改分页拉取 + 中断按钮 + 后端 session 隔离 · 63b3b770
      Data Governance Dev authored
      把原来 /api/queries/run 单次同步全表扫描改成 3 个端点:
        POST /api/queries/start    COUNT + 编译规则 + 建 session(UUID)
        POST /api/queries/page     按页号拉一页,跑规则,只返回不合规的行
        POST /api/queries/cancel   摘掉 session(best-effort 中断)
      
      session_manager.py 用模块级 dict + Lock 存 QuerySession,TTL 30min 懒清理。
      每页新开 DBConnection → fetchall(避开 cursor 生命周期管理),~50ms 重连可接受。
      
      dialect 兼容:
        mysql    : LIMIT <n> OFFSET <m>
        dameng   : OFFSET <m> ROWS FETCH NEXT <n> ROWS ONLY (SQL:2008)
        oracle   : ROWNUM 三层嵌套(兼容 11g/10g;T4 任务实测 11g 服务端用 OFFSET/FETCH NEXT 报 ORA-00933)
        postgres / sqlserver : 同 dameng
      
      前端友好的关键点:
        - /start 一次返回 fields(含 rule_list + field_comment),前端 resultFields 不再变
        - 后端给每行注入 __row_index = (page_no-1)*page_size + idx,给前端做 Vue row-key
        - /page 失败不摘 session,让前端可以重试或走 /cancel
      
      测试:新增 web3/tests/test_queries.py 15 条(start / page / cancel / paginate_sql / 并发 / TTL),
      老的 test_run_query_only_bad_rows.py 和 test_rule_types_e2e.py 适配新端点(删 max_rows 那条,
      end-to-end 改成 /start + /page 聚合)。73 条全过。
      63b3b770
    • Data Governance Dev's avatar
      fix(web3): 空数据时隐藏结果表「备注」列 · 9263f8d7
      Data Governance Dev authored
      无任务 / 选了任务但跑出 0 条不合规行 / 用户还没点「查询」时,
      rows=[],「备注」列还是会被 allColumns 自动追加,整张表只有一列「备注」
      加占位符「—」,看起来很怪。
      
      allColumns 计算属性改成 rows.length > 0 才追加「备注」列。
      rows 为空时整张表只渲染业务列 + 「暂无数据」占位。
      
      Vite HMR 自动生效,不用重启。
      9263f8d7
    • Data Governance Dev's avatar
      feat(web3): date 规则 value 自动 patch 成 datetime 对象再调 check · 135fef49
      Data Governance Dev authored
      问题(commit 5ffa4cc4 之后又暴露):
      
      用户重跑 test3,attendance_in_time(rule_id=31)仍判不合规。
      诊断:pymysql/oracledb/dmPython 把 DATETIME 字段自动转成 Python datetime 对象,
      但用户规则代码里的 strptime 用了 '%Y-%m-%d'(只接受纯日期),
      而 str(datetime_obj) 是 'YYYY-MM-DD HH:MM:SS'(空格分隔的日期+时间),
      格式不匹配 → ValueError → except 吞 → False。
      
      修法:在 run_rule 调 check 前先做一次宽泛解析,把任意类型的 val 归一化成
      datetime.datetime 对象。用户写规则直接 value.date() / .year / .strftime() 用,
      不再关心 val 的原始类型(驱动转的 datetime 对象 / str / bytes / None)。
      
      1. backend/core/rule_runner.py
         - 新增 _DATE_PARSE_FORMATS:10 种常见格式(MySQL DATETIME 默认 / ISO8601 /
           紧凑 / 斜杠分隔 等)
         - 新增 _patch_date_value(val):datetime/date/str/bytes → datetime.datetime,
           解析失败返 None
         - run_rule 的 date 分支先 patch:成功 → _exec_user_function(code, dt);
           失败 → 抛 RuleRunError('无法解析为日期:...') 让 issues 暴露原因
      
      2. backend/core/ai_rule.py
         - _DATE_USER_TEMPLATE_INITIAL / _DATE_USER_TEMPLATE_FIX 完全改写:
           「value 已经是 datetime.datetime 对象(后端已自动宽泛解析)」
           去掉所有 datetime.datetime.strptime 教学,改用 value.date() / .year 等
      
      3. src/components/RuleModal.vue
         - codePlaceholder(date) 改 'def check(value) -> bool:\n    return value.date() < datetime.date.today()'
         - date 类型 form-hint 改 'value 已是 datetime 对象;不要再 strptime'
           (红字警示)
      
      4. tests/test_rule_runner.py
         - 删 1 条过时的 strptime 风格用例 test_strptime_format_check
         - TestDate 加 8 条用例:patch 后传 datetime / datetime 对象直接传 /
           MySQL 默认格式 / ISO8601 / 紧凑 / 斜杠 / bytes / 解析失败抛 RuleRunError /
           None 抛 RuleRunError / 老 strptime 风格代码在 patch 下返 False(迁移信号)
         - 新 TestPatchDateValue 类 11 条用例:锁 _patch_date_value 的 12 种类型行为
      
      5. tests/test_rule_types_e2e.py
         - test_queries_run_dispatches_by_rule_type 里 birthday 规则的 code
           改成新风格 'value.date() <= datetime.date.today()'
      
      对老规则的影响(migration gap):
      - DB 里的旧 strptime 风格代码(rule_id=28 / 31)继续返 False —— 行为不变,
        用户需去 UI 改成新风格。按 CLAUDE.md「本工具只做查询」不动 DB。
      - 新生成的或手写正确代码直接用 value.date() 等,跟 patch 行为兼容。
      
      60 条测试全过(+9 新增)。
      135fef49
    • Data Governance Dev's avatar
      fix(web3): 日期规则 datetime.strptime 缺模块限定符 → datetime.datetime.strptime · 5ffa4cc4
      Data Governance Dev authored
      沙箱里 datetime 是模块名(不是 from datetime import datetime),
      datetime.strptime / datetime.now 在沙箱里直接 AttributeError。
      
      三处误导性的写法全部修正:
      
      1. backend/core/ai_rule.py
         - _DATE_USER_TEMPLATE_INITIAL / _DATE_USER_TEMPLATE_FIX 两份 prompt
           把 datetime.strptime 改成 datetime.datetime.strptime,
           并加粗警告『绝对不能写成 datetime.strptime(会 AttributeError)』
         - 顺手修一笔 def check(value) -> str -> bool 的笔误(多写了 -> str)
      
      2. src/components/RuleModal.vue
         - codePlaceholder(date) 改用 datetime.datetime.strptime,
           注释里点明沙箱语义
         - date 类型的 form-hint 改用 datetime.datetime.strptime,
           并加红字 warning 警示『不是 datetime.strptime』
      
      3. tests/test_rule_runner.py
         - TestDate 加 test_datetime_strptime_without_module_qualifier_fails_clearly
           锁定『datetime.strptime(缺 .datetime.)必然 AttributeError』这个行为,
           防止哪天有人悄悄做成兼容(破坏语义清晰度)
      
      注:已有坏规则(如 attendance_in_time 已落库 4 处)用户自行到 UI 改,
      按 CLAUDE.md『本工具只做查询』原则不动 DB。
      39 条测试全过。
      5ffa4cc4
    • Data Governance Dev's avatar
      feat(web3): 编辑场景自动从 DB 同步字段注释/类型 · 1dffa7f9
      Data Governance Dev authored
      背景:
      cn / type 故意不持久化(plan §4),编辑时按 source_table 重新从数据源拉。
      但用户每次都得手动点「测试连接」走 loadColumns,体验差。
      
      改动(只动 web3/src/components/TaskModal.vue):
      
      1. 新增 refreshColumnsForEdit():
         - 用存储的 conn_json(含匹配的预设密码或后端返回的密码)+ source_table
           直接调 listColumns API
         - 按 field_key 索引 merge 进现有 fieldList(不动 stored rules,只覆盖 cn/type/isNullable)
         - DB 中已不存在的字段 → 保留 stored 状态 + 顶部 warning
         - DB 中新出现的字段(无 stored rules)→ 不主动加,避免污染用户视角
         - API 失败(密码空/DB 不通/表被删)→ 静默退化,不弹错误
      
      2. form.password 回填优先级:task 自带 > 预设 > 用户手输
         (旧版只靠预设匹配回填,手动建的任务没匹配预设就拿不到密码)
      
      3. 触发时机:编辑场景打开弹窗后,props.task.field_list 异步到位时
         (不能挂 open watch:父组件 openEdit 是「同步 set row + 异步 getTask」
         两步,同步阶段 field_list 还是 [],refreshColumnsForEdit 会 no-op)
      
      4. 字段表头部「↻ 重新同步字段」按钮:DB 中途变更后手动刷新
         v-if=props.task && form.table && fieldList.length > 0
      
      5. preset 加载用 props.task.db_type(不再用 form.dbType 默认 'MySQL'):
         编辑 Oracle 任务时,open watch 同步阶段 preset 匹配拿的是 MySQL 预设,
         永远匹配不上;watch(form.dbType) 异步重载又来不及。
         改为先用 targetDbType=props.task?.db_type||form.dbType 加载。
      
      踩坑:见 work-logs/2026-08-21.md(同步/异步时序类 + 密码可见性)。
      1dffa7f9
    • Data Governance Dev's avatar
      feat(backend): 详情接口返回 task 密码 + 守住 list/detail 可见性 · 03e74bbb
      Data Governance Dev authored
      旧设计:密码不进 API 响应(标榜'安全'),靠前端从匹配的 connection_preset 里拿。
      问题:业务用户手动建的连接没匹配的预设 → form.password 永远空 →
           /api/connect/columns 因为缺密码连不上 → 编辑场景 cn/type 永远是 '—'/'空'。
      
      改为:详情接口(GET /api/tasks/{id})直接返回 password 字段;列表接口不返回。
      
      权衡:
      - 明文传输:内部工具单用户部署,TLS 终端到终端,加密不加密收益相同
        (用户反正可以从 task.conn_json 复制、从前端 devtools 看)
      - 真正权威的来源是 task.conn_json 里的密码(用户建任务时填的那个),
        而不是预设的密码(可能跟任务不同步)
      
      测试 test_password_returned_in_detail_but_not_in_list 守住:
      detail 接口 password 有值,list 接口 password 为 None。
      
      如果以后需要让 list 也带,加 ?include_password=true 查询参数即可;
      现在不预先暴露。
      03e74bbb
    • Data Governance Dev's avatar
      fix(web3): RuleModal 测试按钮「未填写」提示优化 · a07af9cc
      Data Governance Dev authored
      之前 code 为空只笼统提示「未填写」,用户难定位是哪个框没填;
      现在分别提示「请先填写正则表达式」/「请先填写 Python 函数」/
      「请输入测试值」,避免和 textarea 的 placeholder 灰色示例混淆。
      a07af9cc
    • Data Governance Dev's avatar
      feat(web3): 规则扩展为数值/日期类型(Python 函数 + 安全沙箱 + AI 生成) · 8ea2935f
      Data Governance Dev authored
      需求:规则这里增加数值和日期规则(可以切换),同样可以使用AI生成,
      目标结果是一个python函数,测试也是调用后台测试。
      
      DB:
      - rule 表新增 rule_type(regex/number/date,默认 regex)/ code(Python 函数源码)
      - init_db() 自动 ALTER ADD COLUMN(每个 ADD 都带 DEFAULT,老规则仍按 regex 走)
      
      后端:
      - core/rule_runner.py(新增):run_rule(rule_type, regex, code, value)
        - regex 走 re.compile.search
        - number/date 走 _exec_user_function(白名单 exec 沙箱)
        - 白名单 __import__:datetime/time/_strptime/math/decimal/re
          (strptime 内部 lazy-import _strptime,所以不能一刀切屏蔽 __import__)
        - 沙箱预注入 datetime/time/math/decimal/re 到 globals
        - validate_user_code():compile + 必须 def check(value) -> bool
      - core/ai_rule.py(新增):gen_rule(desc, rule_type)
        - regex 复用老的 ai_regex.gen_regex(行为完全兼容)
        - number/date 走 LLM,validate_user_code 校验,5 次重试
      - models/rule.py:Rule 加 rule_type/code + to_dict + RULE_TYPES 常量
      - routers/ai.py:
        - 新增 /api/ai/rule(POST {desc, rule_type} → {ok, code, note})
        - 新增 /api/rule/test(POST {rule_type, code, value} → {ok, pass_, error})
        - 保留老 /api/ai/regex 和 /api/regex/test(薄壳转发,老测试不挂)
      - routers/tasks.py:RulePayload/RuleOut 加 rule_type/code,按类型持久化
      - routers/queries.py:按 rule_type dispatch,number/date 走沙箱;
        沙箱报错(语法错 / 缺 check)也按不合规处理,把错误原因写进 issues
      
      前端:
      - src/api/ai.js:genRule/testRule 统一接口(rule_type 参数化)
      - src/components/RuleModal.vue:
        - 规则类型 tab 切换(正则/数值/日期)
        - regex 走单行 input;number/date 走等宽 Python 编辑框
        - AI 按钮文案按类型变(生成正则/数值校验函数/日期校验函数)
        - 测试统一走 /api/rule/test,保证和检测引擎语义一致
      
      测试:
      - tests/test_rule_runner.py(新增,20 个用例):
        - regex 简单匹配 / 空正则 / 非法正则
        - number 校验函数 / 非法输入 / 缺 check / 运行时异常
        - date strptime 校验 / 范围校验
        - 沙箱安全:os 拒绝 / datetime 预注入 / open 拒绝 / eval 拒绝 / getattr 拒绝
        - validate_user_code 4 项
      - tests/test_rule_types_e2e.py(新增,5 个用例):
        - 持久化三种 rule_type
        - 默认 rule_type=regex(向后兼容)
        - queries/run 按 rule_type dispatch
        - 规则执行失败标记为不合规 + 暴露原因
        - 老 /api/ai/regex 端点兼容
      
      迁移:老规则行 rule_type='regex'、code=NULL,自动走旧逻辑(不动现有数据)。
      8ea2935f
    • Data Governance Dev's avatar
      refactor(web3): 去掉 Oracle Client / 连接参数 输入框,配置由后端统一管 · 23151684
      Data Governance Dev authored
      UI 简化:
      - 去掉「Oracle Client」输入框(v-if=Oracle 时显示)—— 这个路径由连接预设
        (connection_presets.oracle_client_dir) 配置,seed.py 里「黄冈公积金」那条已带,
        用户不应手填
      - 去掉「连接参数」输入框(jdbcParams)—— 三种数据库都没用
      
      字段链路保留:
      - form.oracleClientDir 字段不删除,仅模板不展示
      - presetToForm 透传 preset.oracle_client_dir → form.oracleClientDir
      - 编辑场景从 task.oracle_client_dir 回填
      - onSave 写进 conn_json → 后端 DBConfig 拿到
        —— 这样选预设 / 编辑现有 task 时 Instant Client 路径仍正确传递,
        UI 干净 + 链路完整两不误
      
      presetToForm 同步去掉 jdbcParams 字段(form 已经没有该字段)
      23151684