# FIX-03 修复报告：RL-007 刃长 vs 悬伸语义修正

**日期**：2026-10-16
**决策方案**：**方案 B**（总纲第 9 节默认授权：刃长比降为 info 提示；真刚性判定等 `overhang_mm` 数据再启用）
**审计来源**：验收方已出具 `tools\audit_tool_data.py` + `状态\tool_data_audit.json`，本报告不重复审计，只引用并落地。

---

## 一、结论先行（回答卡片三问）

### Q1：RL-007 到底判"刃长/刀径"还是"悬伸/刀径"？

**最终语义（已写进规则 JSON，数据驱动）**：

| 量 | 字段 | 性质 | 用途 |
|---|---|---|---|
| 刃长比 | `flute_length_mm / diameter_mm` | 刀具**规格**（厂商样本值，装深装浅不变） | 仅 info 级选刀参考 |
| 悬伸比（刚性判据） | `overhang_mm / diameter_mm` | **装夹状态**（刀柄夹持后伸出总长） | >4D warning、>6D error + 降额 25% |

**缺字段降级行为**：刀具数据无 `overhang_mm` 时（当前全库 43 把全无），刚性判定**整体跳过**——
不做 warning/error、不降转速/进给，只保留刃长比 info 提示。绝不再拿刃长硬套悬伸。

### Q2：tools.json 的 D12/D8 刃长是旧 XML 的真实值吗？

**是，前 10 把刀逐字段比对 100% 一致**（见第三节核实表）。迁移忠实，未篡改数据。
但旧 XML 的 `FluteLength` 语义本身就是"切削刃长度"，不是悬伸——数据真实，口径用错了。

### Q3：现在这条警告是"真该报"还是"误报"？

**按旧口径（冒充刚性警告 + 降参数）= 误报**：
- 43 把刀 34 把（79%）触发，从 φ0.5 钻头到 φ42 面铣无一幸免；
- 真悬伸取决于车间装夹，库里根本没有这个数据，刚性结论无从谈起；
- 其中 3 把刃长比 >6D（E0.5/E1/E2 钻头、M3 丝锥等）还被**错误降了 25% 参数**——
  典型如 M3 丝锥曾因"刃长比 6.67D"被降速（Fz=0.375 事故链同源）。

**按新口径（info 选刀参考）= 该报**：刃长比大的刀（如加长刃）确实更容易让刀/振纹，
作为**参考信息**有工程价值，但必须标明"非刚性判据、参数不降低"，否则就是说假话。

---

## 二、审计事实（引用验收方结论，未重复审计）

`状态\tool_data_audit.json`（audit_tool_data.py 产出）：

- 全库 43 把刀，`missing_flute_length = 0`（刃长字段齐全）；
- `fires_rl007 = 34`（79%，旧口径下）；
- `has_any_overhang_field = false`（**无任何悬伸字段**：无 overhang_mm / gauge_length_mm / stickout_mm）；
- 典型：D12 flute=50 → 4.17D；D8 flute=40 → 5.0D；E0.5 flute=8 → 16D。

根因是**语义错误而非数据错误**：刃长是规格、悬伸是装夹，拿规格当装夹必然大面积误报。

## 三、旧 XML 数据核实（卡片第 2 条，本次新做）

源：`C:\NxCamToolkit\data\ToolLibrary.xml`（旧库，只读，未改动）。
旧 XML 每条刀具有 `Length`（刀具总长）与 `FluteLength`（切削刃长度）两个独立字段。

std `tools.json` 前 10 把（按 legacy_id 1~10）与旧 XML 逐一比对：

| 名称 | XML D / Length / Flute | std D / flute / length_mm | 一致 |
|---|---|---|---|
| D12 | 12 / 75 / 50 | 12.0 / 50.0 / 75.0 | ✅ |
| D10 | 10 / 70 / 45 | 10.0 / 45.0 / 70.0 | ✅ |
| D8 | 8 / 60 / 40 | 8.0 / 40.0 / 60.0 | ✅ |
| D6 | 6 / 55 / 35 | 6.0 / 35.0 / 55.0 | ✅ |
| D5 | 5 / 50 / 30 | 5.0 / 30.0 / 50.0 | ✅ |
| D4 | 4 / 45 / 25 | 4.0 / 25.0 / 45.0 | ✅ |
| D3 | 3 / 40 / 20 | 3.0 / 20.0 / 40.0 | ✅ |
| D2 | 2 / 35 / 15 | 2.0 / 15.0 / 35.0 | ✅ |
| D1 | 1 / 30 / 8 | 1.0 / 8.0 / 30.0 | ✅ |
| D0.5 | 0.5 / 25 / 5 | 0.5 / 5.0 / 25.0 | ✅ |

**结论**：刃长与总长均为旧 XML 真实值，10/10 一致，转换过程无数值篡改。
旧数据本身可靠，无需标"待用户确认"；问题只在 RL-007 把 `FluteLength` 当悬伸用。
按红线要求**未改任何刀具数据**。

## 四、改动清单（全部在 D:\NX_AI 下，改前已备份到 backup\*.before_FIX03_20261016_203537）

1. `data\rules\ai_programming_rules_v2.json` —— RL-007 规则重写（数据驱动）：
   - title/priority：降为 `info`；rule 文案写清刃长≠悬伸、缺字段降级、方案 A 升级路径；
   - 新键：`requires_field=overhang_mm`、`missing_field_behavior=skip_rigidity_check_keep_flute_info_only`、
     `flute_ratio_note_ratio=4.0`、`flute_ratio_note_severe_ratio=6.0`、
     `rigidity_warn_ratio=4.0`、`rigidity_severe_ratio=6.0`（阈值全部数据驱动，不硬编码）；
   - 保留 `overhang_param_reduction_pct=0.25` 供未来真悬伸通道使用。
2. `data\rules\rule_engine.py`：
   - `ToolInfo` 新增可选字段 `overhang_mm: Optional[float] = None`；新增 `flute_ratio`（语义准确别名）、
     `rigidity_ratio`（缺字段返回 None）；旧 `overhang_ratio` 保留但注释标明实际是刃长比（兼容不破坏）；
   - Step 7 拆为 7a（刃长比 info，不降额）+ 7b（真悬伸 warning/error + 降额；丝锥仍只降转速保 Fz=螺距）。
3. `core\propose_plan.py` —— `tool_from_std` 读取 `geometry.overhang_mm`：未来工艺员补录后
   **零代码改动自动启用**真刚性通道（方案 A 闭环已铺好）。
4. `tests\test_cutting_rules.py` —— Case 7 重写为新语义 6 组断言；
   **顺带修复 FIX-02 遗留测试债**：Case 6/9 在 RL-009 接管 finishing Ap 后已悄悄失效
   （RL-009 先夹到 0.3mm，RL-006 的 0.4D 判断永不可达），改为注入越界 roughing 预设验证 RL-006 保险栓。
5. `tests\run_eval.py` —— RL-007 门禁断言更新：刃长比 >4D 必须有 info、严禁 warning/error、
   必须处于 overhang_mm 缺失状态。

## 五、验证结果

| 验证 | 结果 |
|---|---|
| `test_cutting_rules.py` | **9/9 PASSED**（含新 Case 7 六组断言：4.17D/6.67D 均 info 不降额、短悬伸 3D 不降、真悬伸 4.5D warning、7.5D error 降 25%、丝锥 8D 只降转速 Fz=螺距） |
| `test_finishing_ap.py` | 9/9 PASSED（无回归） |
| `test_launcher.py --unit-only` | 5/5 PASSED |
| `run_eval.py` | **通过 13/13，意外退化 0，特征化基线漂移 0**（无需重建：基线只存 rule_id，且基线刀具刃长比均 <6D，旧逻辑本就未降额） |
| `check_py_syntax.py` | 31 个 .py，0 错误 0 预警 |
| 全库 43 把刀统计（修复后） | **info 34 / warning 0 / error 0 / 无提示 9，错误降参数 0 起** |
| 负向指标对比 | 示例工序单 violations：error 3→0、warning 3→1（噪声实质性消除） |

## 六、方案 A 升级路径（未来补数据时怎么做）

1. 工艺员/对刀仪提供每把刀在当前装夹下的真实悬伸，写入 `tools.json` 的 `geometry.overhang_mm`；
2. 无需改任何代码：`tool_from_std` 自动透传，引擎 Step 7b 自动启用刚性判定与降额；
3. 刃长 info 提示仍保留（两种信息互不冲突，可同时出现）。
