P10-07C 刀具库 / 材料库 / 参数库做到最优（数据质量卡）

## 用户意图（2026-10-20 11:59）
「我们先把刀具、材料、参数做到最优。暂时自动出程序。等模拟好了，我自己出程序。」
→ 本卡只做**数据与规则的最优化**，不碰界面、不碰 NX 调用、不碰云端。
→ 目标：用户打开工序单，看到的是"可信、可解释、可覆盖"的参数。

## 一、刀具库（data\std\tools.json 升级）
每条刀具必须含：
```
tool_id, name, type(mill/drill/tap/thread/ball/radius), diameter_mm,
flute_length_mm, overall_length_mm, flutes, coating, 材质(HSS/硬质合金/涂层),
T号(1-20), 适用材料族[], 备注
```
规则：
- T 号唯一、范围 1–20；缺失或重复 → 校验失败并指出是哪两条
- 刀具名解析：`D12`→φ12 平底；`E8.5`→φ8.5 钻孔；`M8`→M8 攻牙（螺距 1.25）；
  `R6`/`B6`→球刀；`T12R1`→圆鼻刀（φ12 带 R1）
- 提供 `--validate` 校验命令 + `--export-csv` 便于人工审阅
- **不改动已有刀具的既有字段值**（评测基线可能依赖）

## 二、材料库（data\std\materials.json + material_families.json 打通）
- 材料条目：`name, aliases[], family_key(AL/ST/STH/SS/HRC/CI/TI), hardness_hb, source`
- 别名要覆盖常见写法：6061/6061-T6/AL6061/铝6061；304/06Cr19Ni10/SUS304；
  P20/1.2311/3Cr2Mo；718/718H；NAK80；SKD11/DC11/1.2379 等
- 解析顺序：精确名 → 别名 → 含关键字的模糊匹配；匹配不到时**明确报"未知材料"+ 建议补齐**，
  不许默默按默认材料算
- 每个族必须能回答："这个材料属于哪族、Vc 取多少、为什么"（可打印）

## 三、参数库（S/F/ap/ae 的权威来源，单一真源）
新建 `data\std\cutting_params.json`，维度：`family × stage(rough/semi/finish) × tool_type × diameter_bucket`
```
示例（AL, rough, mill, D10-12）：vc=325, fz=0.0792, ap_ratio=1.0, ae_ratio=0.3
```
- 现有 `rule_engine.py` 的 SpeedFeedTable 逻辑要**收敛到这张表**（可以保留旧入口做兼容层，
  但计算必须能从表里查到，不许两套并存各算一套）
- 每条参数必须带 `source`（厂商样本/车间实测/用户录入）与 `updated_at`
- 用户覆盖优先：`overrides` > `cutting_params.json` > 内置默认；
  永久记忆里的修正优先级最高（P10-04 机制）

## 四、一致性校验（关键，防"两套算法打架"）
新建 `tools\params_audit.py`：
```
--check-materials    每个材料能否解析到族；族区间是否完整
--check-tools        刀号唯一性/范围/名称解析是否自洽
--check-params       表内每条是否在族区间内；超出则告警并给出建议
--cross-check        对同一 (材料, 刀, 阶段) 用旧引擎与参数表各算一次，
                     差异 >5% 即列出来（便于人工裁决，不许自动选一个）
```
验收时 `--cross-check` 的输出必须为空或**逐条说明为什么不同**。

## 五、与工序单的呈现
工序单每行参数旁加"来源"标记：
```
A01 面铣 D12  S=8620.9 F=2047.5 ap=12.0 ae=3.6   [族AL/粗/表1.0×D]
```
让人一眼看出参数从哪来、是不是被覆盖过。

## 六、验收（实测）
1. `tools.json --validate` 输出（T 号唯一、范围、名称解析全通过）
2. 材料解析测试：`6061-T6`、`06Cr19Ni10`、`1.2311`、`NAK80`、`DC11`
   各自解析到哪个族（贴出），并贴一个"未知材料 XYZ"的正确报错
3. `cutting_params.json` 结构 + 至少 AL/ST/SS/HRC 四族 × 三阶段的完整行
4. `params_audit.py --cross-check` 真实输出（空或逐条解释）
5. 同一 (304, D12, rough) 从工序单到计算全程可追溯（贴出链路：材料→族→表项→公式→结果）
6. 门禁全绿（评测 21/21 必须不变）+ 新增 .py 含 `P2-02 控制台编码护栏`

## 约束
- 不改已有刀具/材料键的既有数值（评测基线）
- 不改 tapping_speed.json 的 N 组 12.5
- 不碰 NX 调用、不碰云端、不碰界面
- 完成后写 `状态\P10-07C.done.json`

## 追加：阶段与刀具的分配规则（用户 2026-10-20 12:11，修正 P10-05 的表述）
用户原话：「铣刀要有粗加工刀和精加工刀。有公差要求的地方，需要我把相关工序勾选半精加工，
这样精加工的刀需要再做一刀半精加工，用于给下一刀精加工做参考，尺寸不对，要调整的。」

### 准确规则（以此为准）
```
粗加工：用【粗加工刀】（大刀，如 D12，T1）
半精加工：用【精加工刀】（小刀，如 D8，T2）走一刀，留 0.05
精加工：同一把【精加工刀】再走一刀，余量 0 或手动值
```
- **粗刀 ≠ 精刀**（不同直径、不同刀号）
- **半精与精 = 同一把刀**（这是"用精刀做半精"，为下一刀精加工留参考）
- 半精是**可选**的：默认只做 粗 + 精；
  **有公差要求的部位才勾选半精**，没勾就不生成半精程序

### 工序单对应列
```
[加工阶段] 粗加工 / 半精加工 / 精加工
[勾选半精] 复选框（只有公差要求处才勾）
[刀具]     粗刀名或精刀名（由阶段决定，不许手填成同一把）
```
界面校验：
- 若"粗加工"与"精加工"两行用了同一把刀 → 标红提示"粗精应分刀"
- 若勾了半精，则半精与精加工的刀具名必须**一致**，否则标红

### 半精的作用（用户强调）
半精不是最终尺寸，它是**精加工的参考刀路**：
- 半精后测尺寸 → 与目标比对 → 差值修正精加工余量（复用 P10-05 的 stage_comp 机制）
- 修正结果写 overrides + 永久记忆（原因写"半精实测 X 目标 Y"）
- 精加工再走一刀把尺寸做到位

### 验收（实测）
1. 贴出一次三阶段生成的刀具分配：粗 D12/T1、半精 D8/T2、精 D8/T2（证明粗精分刀、半精精同刀）
2. 贴出"未勾选半精"时的程序：只有粗+精两个 NC，**没有**半精 NC
3. 贴出"勾选半精"时的程序：三个 NC，且半精与精用同一把刀
4. 构造一次尺寸超差（target 20.00 / measured 20.06），贴出精加工余量被修正为 -0.06 的证据
5. 门禁全绿

## 二次修正（用户 2026-10-20 12:20，以此为准，覆盖上一节的"必须三段"）
用户原话：「没有公差要求的，半精加工和精加工可以用同一把刀，就是 UG 里的增加精加工刀路
这个就可以了。只有有公差要求的精加工刀需要另外加，粗加工+半精加工+精加工，后面两个同一把刀，
但是要分开做。做完半精加工要必须加 M01，测量尺寸后，再精加工。」

### 两种模式（按是否有公差要求）
```
模式 A · 无公差要求（默认）
  粗加工（粗刀 T1）
  → 同一工序内勾选「增加精加工刀路」(NX 的 finishing pass，用精刀 T2)
  → 输出 2 个程序：A01_粗、A02_精
  → 不需要中间测量，不拆半精

模式 B · 有公差要求（勾选半精）
  粗加工（粗刀 T1）
  → 半精加工（精刀 T2，留 0.05）        独立程序 A02_半精
  → **必须 M01**（半精程序结尾强制加）
  → 操作工测量尺寸 → 修正精加工余量（stage_comp）
  → 精加工（同一把精刀 T2，做到位）      独立程序 A03_精
  → 输出 3 个程序；半精与精必须同一把刀但分开做
```

### 界面语义（工序单）
```
[加工阶段]       粗加工 / 精加工（模式A，一道工序带精工刀路）
                 或 粗加工 / 半精加工 / 精加工（模式B，三段分开）
[勾选半精]       仅在"有公差要求"处勾选 → 触发模式 B
[增加精加工刀路] 模式 A 下勾选（NX 内置 finishing pass，不额外拆程序）
```
校验：
- 模式 A：粗刀 ≠ 精刀；精工刀路与粗加工在**同一工序**内
- 模式 B：粗刀 ≠ 精刀；半精与精**同一把刀、两个独立程序**；半精程序必须含 M01
- 勾了半精却没给公差要求说明 → 提示"请写明公差要求（例如 ±0.02）"

### M01 的强制要求（模式 B）
- 半精程序结尾：`M05 M09 → 回参考点 → (=== 半精完成，请测量 XX 尺寸，公差 ±0.0X ===) → M01 → M30`
- 精加工程序**不得**在半精未确认时自动接续（即不能把半精和精写进同一 NC）
- 测量值与目标值必须记录（target/measured/delta），修正精加工余量后才生成精加工程序

### 验收（实测，替代上一节验收）
1. 模式 A 实跑：贴出只有 2 个 NC（粗/精），且精加工刀路在**同一工序**内（NX 工序列表证明）
2. 模式 B 实跑：贴出 3 个 NC，半精与精同一把刀、两个独立文件
3. 模式 B 的半精 NC 结尾必须有 M01 + 测量提示（贴原文）
4. 模式 A 不产生半精 NC、不含 M01 测量提示
5. 尺寸修正链路：target 20.00 / measured 20.06 → 精加工余量 -0.06（贴 overrides 与记忆条目）
6. 门禁全绿

## 追加硬性要求（用户 2026-10-20 15:18）
「铣刀进刀的话，非切削移动里面的进刀方式改成螺旋进刀。」

### 所有铣类模板必须满足
NX 路径：`工序 → 非切削移动(Non-Cutting Moves) → 进刀(Engage) → 进刀类型 = 螺旋(Helical)`

适用模板（全部改为螺旋）：
```
TPL_FACE, TPL_SIDE, TPL_POCKET_R, TPL_POCKET_S, TPL_POCKET_F,
TPL_ZLEVEL, TPL_HOLEMILL, TPL_SURFACE
```
不适用（孔类本来就是轴向进给）：TPL_DRILL, TPL_TAP, TPL_THREAD

螺旋参数要求：
```
螺旋直径      = 0.5 ~ 0.9 × D（默认 0.6D）
螺旋角        = 1° ~ 3°（默认 2°）
最小斜面长度  = 20% ~ 30% × D
退刀方式      = 与进刀相同，或 最小(minimal)
```
例外（必须显式声明并说明理由）：
- 面铣/侧面若"从工件外部下刀"，允许 linear，但必须在模板定义里标
  `engage_linear_reason = "open_area_outside_engage"`，
  且 template_check 输出里要能看到这个例外标记（不许默默用直线）

### 校验要求（template_check.py）
- `--check <TPL>` 必须**从 .prt 里读回实际 engage 类型**（不是读配置 json）
- 铣类模板若读回 linear 且无例外标记 → 判定失败，exit 非 0
- 输出格式：`进刀=helical_clw（螺旋顺时针）` 或 `进刀=linear（例外：open_area_outside_engage）`

### 当前已知不达标（必须修）
```
TPL_FACE  进刀=linear  ← 需改螺旋（或加例外标记）
TPL_SIDE  进刀=linear  ← 同上
TPL_POCKET_R/S/F, TPL_ZLEVEL 已是 helical_clw ✅
```
修完后需重新生成 `nx_templates\NXAI_工序模板.prt`，并用独立进程重新读回验证。

### 验收
1. `template_check.py --check all` 输出里，8 个铣类模板的进刀类型全部为 helical
   （或有明确例外标记），exit=0
2. 用独立进程打开重新生成的 .prt，读回 engage 类型（贴出原文）
3. 贴出 TPL_FACE 修改前后的对比（linear → helical）
