# 修复卡（评测门禁发现）

> 来源：验收方 2026-10-16 建立 **防退化评测门禁**（`D:\NX_AI\tests\run_eval.py`）后首次运行即抓出。
> 全部有可复现证据。

---

### FIX-04 【已由验收方修复并验证】丝锥 Fz 被悬伸降额改错对象

```text
【任务 FIX-04】（收尾）复核验收方的修复 + 补单元测试与根因文档

状态：**验收方已修复并验证通过**，原文件备份在 D:\NX_AI\backup\rule_engine.py.before_FIX04。
      本卡只剩"补测 + 复核 + 文档"，**不要再改修复逻辑**（除非你发现它是错的，那就说明理由）。

一、缺陷现象（修复前实测）
  刀具     螺距     Fz       F       应F      悬伸比   rpm      判定
  M3     0.500   0.3750   318.3   424.4    6.67    848.8    ✗ 违反「Fz=螺距」
  M4     0.700   0.7000   445.6   445.6    5.50    636.6    OK
  M5     0.800   0.8000   407.4   407.4    5.00    509.3    OK
  （M6/M8/M10/M12 也 OK；只有 M3 中招）

二、根因（验收方定位，rule_engine.py 第 369~384 行 + 第 386~398 行）
  Step 7 的 RL-007 悬伸降额，实现是：
        feed *= (1 - overhang_param_reduction_pct)      # 降的是 feed
  而 Step 8 的 fz 是从降额后的 feed **反算**的：
        fz_calc = feed / (rpm * teeth_for_calc)
  → 丝锥的 fz 就被"顺带"缩放成了 0.5 × 0.75 = 0.375。
  为什么只有 M3：它的悬伸比 6.67D 超过 severe 阈值(6D)，走降额分支；
  其余丝锥 5.5D/5.0D/4.38D/4.0D/3.75D 都只落在 warn 区间(>4D)，只告警不降额。

  **为什么这个缺陷很隐蔽**：修复前后 F 都是 318.3（848.8×0.375 == 636.6×0.5），
  材料去除率一模一样 —— 但丝锥每转进给不等于螺距，上机会乱扣/断丝锥。
  只看 MRR 或进给数值永远发现不了，必须断言 "Fz == 螺距"。

三、修复内容（rule_engine.py Step 7 的降额分支）
  丝锥走单独分支：**只降转速，绝不动进给**
        if tool.is_tap:
            _pitch = tool.tap_pitch_mm
            rpm *= (1 - wr["overhang_param_reduction_pct"])
            if _pitch:
                feed = rpm * _pitch          # feed 由螺距几何锁死
            params.notes.append("RL-007 降额：丝锥只降转速不改进给（Fz=pitch 锁死，红线 RL-002）")
        else:
            feed *= (1 - wr["overhang_param_reduction_pct"])
  依据就是任务书原文：「丝锥 Fz = 螺距，绝不缩放，**保守靠降转速**」。

四、请完成（本卡范围）
1. **补单元测试**：在 `D:\NX_AI\tests\test_cutting_rules.py` 增加一条
   **遍历刀具库全部丝锥**的用例，断言 `fz_mm_per_tooth == tap_pitch_mm`（容差 1e-9）
   且 `feed_mm_min == rpm * tap_pitch_mm`（容差 1%）。
   —— 原来的测试只测了部分情形，正是"没遍历全部丝锥"才让这个 bug 活了下来。
   同时保留"故意改坏规则必须失败"的元测试风格。
2. **复核并书面确认**我的修复：读 `rule_engine.py` 里改动处（搜索 "RL-007 降额：丝锥只降转速"），
   确认逻辑与红线一致。若你认为不对，写清理由并给出替代方案。
3. **写根因文档** `D:\NX_AI\docs\FIX-04_丝锥Fz根因.md`：
   现象 → 根因（含行号）→ 为什么只有 M3 → 修复方式 → 如何防止复发。
4. **检查同类问题**：把 Step 7 的降额逻辑对照 Step 5 的钻头分支检查一遍 ——
   钻头的 `feed = rpm × fz_per_rev`，降额 feed 是否也在改变"每转进给"的语义？
   给出结论（可以不改，但必须明确说明为什么可接受）。
5. 回归：跑 `python D:\NX_AI\tests\run_eval.py`，必须保持
   **通过 12/13、已知未修 1、意外退化 0、退出码 0**；并跑一次 `test_cutting_rules.py`。

验收标准：
  新增的丝锥遍历单测存在且通过；
  run_eval.py 仍为"意外退化 0"；
  FIX-04_丝锥Fz根因.md 能回答"为什么 M3 会错而 M4 不会"以及"为什么以前没被发现"。
```

---

### FIX-02 【仍未修】精加工 Ap 算成 4.0mm（要求 0.1~0.3mm）

```text
【提醒 FIX-02】精加工 Ap 语义错误 —— **已由验收方修复**（原卡与收尾要求见 `04_修复卡_自动编程管线实测.md` 的 FIX-02，本处不重复下卡）

现状：门禁 `D:\NX_AI\tests\run_eval.py` 里 RL-006 精加工Ap区间 仍是 FAIL，
      已被登记为"已知未修"（D:\NX_AI\tests\golden\known_issues.json，卡号 FIX-02）。
      它的实测现象、根因排查方向、验收标准见 `D:\NX_AI\queue\FIX-02.txt`（原卡不变）。

另外提醒：修好之后**必须**从 known_issues.json 里删掉那条记录，
否则门禁会永远显示"已知未修 1"，就失去了"红=有问题"的信号意义。
```

---

### 附录：门禁怎么用（本卡不需要你改它）

```text
D:\NX_AI\tests\run_eval.py                防退化评测门禁（三道闸，13 条断言）
D:\NX_AI\tests\golden\baseline.json       特征化基线（10 个上下文的规则输出快照）
D:\NX_AI\tests\golden\known_issues.json   已知未修项记账（当前 1 条：FIX-02）

python D:\NX_AI\tests\run_eval.py                    # 有"意外退化"则退出码非零
python D:\NX_AI\tests\run_eval.py --rebuild-baseline # 规则/数据有意变更后重建基线
```

验收方已实测确认门禁本身有效：
- 故意篡改基线里某个 rpm → 报"漂移 1 处"并打印 old→new；恢复后归零
- "已知未修"与"意外退化"分开记账，前者不淹没后者
- 修好 FIX-04 后：**通过 12/13、已知未修 1、意外退化 0、退出码 0**

**规则：每次改完规则/数据/学习层，都必须跑一次且"意外退化 = 0"。**
