Commit a53fa3b1 authored by Data Governance Dev's avatar Data Governance Dev

refactor(step6): 字段长度检查改为纯规则匹配,移除 LLM 依赖

- step6_length_check.py: 移除 llm 入参与自定义字段 LLM 推断分支,
  只保留内置 GB/YD 14 条规则;签名简化为 run_step6(dict_data, log)
- orchestrator.py: STEP_REGISTRY 中 Step 6 的 llm_mode 由 optional 改为 none;
  _run_step6 不再向 run_step6 传 llm;顶部 llm_mode 取值注释同步
- routes.py: /api/steps 返回的 Step 6 描述去掉「LLM 可选」字样
- README.md: LLM 依赖矩阵删除 Step 6 行,新增说明 Step 1/3/4/6/7/8 均无 LLM 依赖
- DESIGN.md: Step 6 章节由「固定 + LLM 混合」改为「100% 固定代码」,
  删除 suggest_length_rule 示例
- CLAUDE.md: 补充「不要自动提交,我说提交再提交」纪律

验证:
  - ast.parse 通过 (orchestrator.py / routes.py / step6_length_check.py)
  - 烟雾测试:varchar(50) id_card + varchar(20) mobile → 2 条异常,符合预期
parent 3e07d7ac
......@@ -48,4 +48,5 @@ LLM 不是「增强」,对部分步骤是核心依赖:
# 总体要求
- 每做一个任务就在工作记录中记录一次
- 不要自动提交,我说提交再提交
- 每次提交要写清除修改内容
......@@ -194,8 +194,8 @@ LLM 不是「增强」,对部分步骤是核心依赖:
|------|---------|------|------------|
| Step 2 表合并与冗余字段分析 | **required** | 合并 verdict + 高频字段分类 | 任务整体失败 |
| Step 5 缺失注释字段推测 | **required** | 字段语义推测(拼音/英文→中文) | 任务整体失败 |
| Step 4 空字段成因分类 | optional | top 20 高空字段成因分类 | 走规则推理(标 "LLM 未配置") |
| Step 6 自定义字段长度建议 | optional | 自定义字段合理长度推断 | 标记"待业务确认" |
> Step 1 / 3 / 4 / 6 / 7 / 8 均为纯规则匹配,无 LLM 依赖。其中 Step 6(字段长度检查)目前仅依赖内置 GB/YD 标准规则集,未启用 LLM 自定义字段推断。
### 设计要点
......
......@@ -84,8 +84,8 @@ async def list_steps():
description="离线:找无注释字段,LLM 必须:批量推测语义",
requires_db=False, llm_mode="required", required=False),
StepInfo(num=6, title="字段长度检查",
description="离线:识别字段定义长度超出国家标准(LLM 可选:自定义字段推断)",
requires_db=False, llm_mode="optional", required=False),
description="离线:识别字段定义长度超出国家标准(纯规则匹配,不依赖 LLM)",
requires_db=False, llm_mode="none", required=False),
StepInfo(num=7, title="国家标准校验",
description="连库抽样:身份证/USCC/手机号/行政区划符合性",
requires_db=True, llm_mode="none", required=False),
......
......@@ -34,8 +34,8 @@ logger = logging.getLogger(__name__)
# 顺序敏感:前面 Step 的产出可被后面 Step 复用
#
# llm_mode 取值:
# "none" —— 不用 LLM
# "optional" —— LLM 可用就用,失败降级到规则推理(Step 4、6)
# "none" —— 不用 LLM(Step 1、3、4、6、7、8)
# "optional" —— LLM 可用就用,失败降级到规则推理(当前未使用)
# "required" —— 必须 LLM;缺 LLM = 任务失败(Step 2、5)
#
# required 标记:true 表示用户在 UI 上不能取消勾选(如 Step 1 为全部依赖的根基)
......@@ -46,7 +46,7 @@ STEP_REGISTRY: list[tuple[int, str, Callable, bool, str, bool]] = [
(3, "数据验证", "_run_step3", True, "none", False),
(4, "大范围空字段扫描", "_run_step4", True, "none", False),
(5, "缺失注释检查 + 推测", "_run_step5", False, "required", False),
(6, "字段长度检查", "_run_step6", False, "optional", False),
(6, "字段长度检查", "_run_step6", False, "none", False),
(7, "国家标准校验", "_run_step7", True, "none", False),
(8, "报告生成", "_run_step8", False, "none", False),
]
......@@ -436,8 +436,7 @@ def _run_step5(cfg, step_outputs, llm, findings_dir, log, cancel_event):
def _run_step6(cfg, step_outputs, llm, findings_dir, log, cancel_event):
"""Step 6: 字段长度检查(离线)"""
from .step_impl.step6_length_check import run_step6
data = run_step6(dict_data=step_outputs.get("1_data_dict", {}),
llm=llm, log=log)
data = run_step6(dict_data=step_outputs.get("1_data_dict", {}), log=log)
return {"section_key": "length_issues", "data": data}
......
"""Step 6: 字段长度检查(离线 + LLM 增强)
"""Step 6: 字段长度检查(纯规则匹配)
识别字段定义长度超出标准所需(如身份证号 18 位用 varchar(50) 存储)。
基础规则覆盖常见字段;自定义字段由 LLM 推断建议长度。
仅基于内置规则匹配,不依赖 LLM。
产出:
- summary: 总异常数
......@@ -14,8 +14,6 @@ import logging
import re
from typing import Callable
from ..llm import LLMClient
logger = logging.getLogger(__name__)
......@@ -40,8 +38,7 @@ LENGTH_RULES: list[tuple[re.Pattern, int, str, str]] = [
]
def run_step6(dict_data: dict, llm: LLMClient | None = None,
log: Callable | None = None) -> dict:
def run_step6(dict_data: dict, log: Callable | None = None) -> dict:
columns = dict_data.get("data_dictionary", [])
if not columns:
if log:
......@@ -85,87 +82,7 @@ def run_step6(dict_data: dict, llm: LLMClient | None = None,
if log:
log("INFO",
f"[1/2] 规则匹配 {rule_match_count} 次,发现异常 {len(issues)} 条",
step="6")
# 自定义字段(如命名不符合常见模式但 varchar 过大) — 简单启发式
# 仅对没有匹配规则且长度 ≥100 的字段提醒
custom_count = 0
for r in columns:
col_name = r["column_name"]
dt = (r.get("data_type") or "").lower()
max_len = r.get("char_max_length")
if dt not in ("varchar",) or not max_len or max_len < 100:
continue
if any(pat.match(col_name) for pat, *_ in LENGTH_RULES):
continue
if any(i["table_name"] == r["table_name"] and i["column_name"] == col_name for i in issues):
continue
# 大字段且无规则命中 → 候选给 LLM
issues.append({
"table_name": r["table_name"],
"table_comment": r.get("table_comment", ""),
"column_name": col_name,
"data_type": dt,
"column_type": r.get("column_type", ""),
"actual_length": max_len,
"required_length": None,
"wasted_bytes_per_row": None,
"standard": "(自定义)",
"rule": "未匹配任何标准规则",
"issue": f"字段长度 {max_len} 较大,未匹配任何标准",
"suggestion": "需业务确认或 LLM 推断",
"column_comment": r.get("column_comment", ""),
"needs_llm": True,
})
custom_count += 1
if log:
log("INFO",
f"[2/2] 自定义大字段 {custom_count} 条(候选给 LLM)",
step="6")
# LLM 处理 needs_llm 标记的字段(前 20 个)
if llm and llm.available:
to_llm = [i for i in issues if i.get("needs_llm")][:20]
if to_llm and log:
log("INFO", f"[LLM] 调用 LLM 推断 {len(to_llm)} 个自定义字段的合理长度", step="6")
llm_ok = 0
for idx, issue in enumerate(to_llm, 1):
try:
r = llm.suggest_length_rule(
column_name=issue["column_name"],
column_comment=issue["column_comment"],
current_length=issue["actual_length"],
table_name=issue["table_name"],
)
if r:
rec = r.get("recommended")
if rec and rec < issue["actual_length"]:
issue["required_length"] = rec
issue["suggestion"] = f"建议改为 VARCHAR({rec})({r.get('reasoning', '')})"
issue["llm_recommended"] = rec
issue["llm_reasoning"] = r.get("reasoning", "")
issue["wasted_bytes_per_row"] = issue["actual_length"] - rec
llm_ok += 1
if log:
log("DEBUG",
f" · [{idx}/{len(to_llm)}] "
f"{issue['table_name']}.{issue['column_name']}: "
f"{issue['actual_length']} → {rec}",
step="6")
except Exception as e:
if log:
log("WARN",
f"LLM 长度推断失败 ({issue['table_name']}.{issue['column_name']}): {e}",
step="6")
if to_llm and log:
log("INFO", f"[LLM] 推断完成: 采纳 {llm_ok}/{len(to_llm)}", step="6")
if log:
log("INFO",
f"字段长度异常 {len(issues)} 条(其中 {custom_count} 条自定义大字段)",
f"规则匹配 {rule_match_count} 次,发现异常 {len(issues)} 条",
step="6")
return {
......
......@@ -507,8 +507,7 @@ const jobResult = ref(null); // 任务完成后加载
| 4 | 高空字段成因分类 | **LLM**(业务不需要 / 采集漏了 / 预留) | 跳过 LLM 标签 |
| **5** | 字段注释推测(命中规则) | **固定代码**(内置 18 条字段名→注释映射) | N/A |
| 5 | 字段注释推测(未命中) | **LLM**(看字段名+表注释+类型) | 留空 |
| **6** | 字段长度检查(命中规则) | **固定代码**(14 条规则对应 GB/YD 标准) | N/A |
| 6 | 自定义字段长度推断 | **LLM** | 标记"待业务确认" |
| **6** | 字段长度检查 | **固定代码**(14 条规则对应 GB/YD 标准) | N/A |
| **7** | 国标字段校验 | **固定代码**(4 个 standards/ 插件) | N/A |
| **8** | 报告章节拼装 | **固定代码**(Markdown 模板) | N/A |
......@@ -598,27 +597,22 @@ llm.predict_field_comment(
# 返回 {"comment": "工种类别", "confidence": "high"}
```
#### Step 6:固定 + LLM 混合
#### Step 6:100% 固定代码(内置 GB/YD 规则集)
**固定代码**(14 条已知标准):
**实现**(`step_impl/step6_length_check.py`):
```python
LENGTH_RULES = [
(re.compile(r"id_card"), 18, "GB 11643-1999", "身份证号"),
(re.compile(r"mobile"), 11, "YD/T 1313", "手机号"),
...
(re.compile(r"^(id_?card|id_?number|identity_?card)$"), 18, "GB 11643-1999", "身份证号 18 位"),
(re.compile(r"^mobile(_?phone)?$"), 11, "YD/T 1313", "手机号 11 位"),
(re.compile(r"^(uscc|credit_?code)$"), 18, "GB 32100-2015", "统一社会信用代码 18 位"),
(re.compile(r"^(xzqhbm|adcode)$"), 6, "GB/T 2260", "行政区划代码 6 位"),
# ... 共 14 条
]
```
**LLM 增强**(自定义字段):
```python
# varchar(500) 的 remark 字段 → LLM 推断合理长度
llm.suggest_length_rule(
column_name="remark",
column_comment="备注",
current_length=500,
)
# 返回 {"min_length": 50, "recommended": 200, "reasoning": "..."}
```
匹配到规则的字段 → 若实际 `varchar(N)` 的 N 超出标准 → 记为异常,并估算每行浪费字节数。
> Step 6 不依赖 LLM(`llm_mode="none"`)。如需对自定义字段推断合理长度,业务侧按表单独处理。
#### Step 7:100% 固定代码(standards 插件)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment