# 修复卡（验收方实测发现，优先级最高）

> 来源：验收方 2026-10-16 18:00 跑通自动编程管线时实测发现。全部有可复制证据。
> 这些卡排在队列最前（FIX < P），先把地基上的裂缝补掉，再往上盖。

---

### FIX-01 `NxLauncher.py` 是坏文件：语法错误，根本跑不起来

```text
【任务 FIX-01】修复 D:\NX_AI\tools\NxLauncher.py 的语法错误
【状态】**验收方已于 2026-10-16 修复并验证**，本卡只剩"复核 + 补测"。
  原缺陷不止一个，实为**一条 5 连缺陷链**（都因为该文件从未被成功执行过而潜伏）：
    ① 语法错：第 106 行非 raw docstring 里的 `<NX>\UGII\...` → `\U` 触发
       truncated \UXXXXXXXX escape（与 propose_plan.py 同一类事故）
    ② 调 nx_versions.ps1 时把 `-Json` 当开关传（它其实是**输出文件路径**参数）→ 参数绑定失败
    ③ 即使参数对了也解析不到数据：脚本**写文件**、而代码读 stdout
    ④ 字段名不匹配：输出是 `Version`/`Root`，代码用 `target["version"]`/`target["root"]` → KeyError
    ⑤ 缺 `import tempfile`：第 150 行用 `tempfile.mkdtemp` → NameError
  修复后：`--dry-run --version 2412` **退出码 0**，正确列出 NX2212/NX2412 及 E 盘安装根。
  语法门禁（`tools/check_py_syntax.py`）现为 **28 个 .py、0 错误 0 预警**。
  备份：`D:\NX_AI\backup\NxLauncher.py.before_FIX01`
  本卡只剩：复核上述 5 处改动 + 补一条"启动器 detect_nx 能解析出 ≥1 个版本"的集成测试。
  **注意**：部署目标写的是 `<NX>\UGII\vendor\NX Copilot\startup\`，这属于"往 NX 目录塞文件"，
  与用户决定的"不设 UGII_VENDOR_DIR 全局变量"不冲突，但**是否用官方推荐方式加载仍需确认**。

实测证据（验收方用 ast.parse 检查全部 .py）：
  D:\NX_AI\tools\NxLauncher.py  (10 KB, 2026-10-16 15:23:42)
  -> SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 109-110:
     truncated \UXXXXXXXX escape

含义：这个文件连 import/执行都不可能成功。它里面写着"调 deploy.ps1 把 DLL + 三件套部署到
      <NX>\UGII\vendor\NX Copilot\startup\"，说明它是 P0-08/P0-09 交付的部署器，
      但从来没有被真正运行过——否则这个错误第一时间就会暴露。

请完成：
1. 定位并修复语法错误。根因大概率是：**非 raw 字符串/docstring 里的 Windows 路径反斜杠**
   被 Python 当成转义序列（`\U`、`\N`、`\x` 等）。正确写法二选一：
   - 该字符串前加 r：`r"D:\NX_AI\..."`（推荐）
   - 或把反斜杠双写：`"D:\\NX_AI\\..."`
   本卡实测同类坑还出现在我这边的 propose_plan.py（docstring 里 `\NX_AI` 的 `\N`），已修，
   可作为对照参考。
2. **全项目扫描同类问题**：写一个 `D:\NX_AI\tools\check_py_syntax.py`，
   用 `ast.parse` 遍历 D:\NX_AI 下所有 .py（跳过 __pycache__），报告所有语法错误文件。
   这个脚本必须留下，作为以后每次交付前的自检门禁。
3. 修完后**真正运行一次** NxLauncher.py（-DryRun 优先），把输出贴进报告。
   如果它需要管理员权限或会改动 NX 安装目录，只允许跑到 -DryRun，其余写进人工清单。

验收标准：`check_py_syntax.py` 报告全项目 0 个语法错误；NxLauncher.py 至少 -DryRun 能跑通并给出输出。
```

---

### FIX-02 精加工 Ap 严重错误：算出 4.0mm，规则要求 0.1~0.3mm

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

【状态】**验收方已于 2026-10-16 修复并验证**，本卡只剩"复核 + 补测 + 文档"。
  根因：预设里精铣平底刀的 `ap_pct` 被填成 100%（= 1.0×D），而规范要求精加工 Ap 是
        **绝对深度 0.1~0.3mm 不折算**；原实现只做了 0.5D 的 clamp，于是得到 4.0mm
        （铝件 6081 甚至 6.4mm）。
  修复：**数据驱动** —— 新增红线 RL-009，区间写在 `rules JSON` 里（min 0.1 / max 0.3），
        引擎在 Step 6b 对 `operation=finishing` 执行 clamp 并记 warning。
  结果：全部精加工上下文 Ap = 0.300mm；门禁 **13/13 全绿、已知未修 0、意外退化 0**。
  备份：`D:\NX_AI\backup\rule_engine.py.before_FIX02`、`ai_programming_rules_v2.json.before_FIX02`
  详细对照见 `D:\NX_AI\docs\FIX-02_修复报告.md`。
  **不要再改修复逻辑**（除非你能说明它错，那就给出理由与替代方案）。

实测证据（验收方在真实零件上跑 propose_plan.py，材料 ISO P / 45钢退火）：
  工序 finishing  刀具 D8   S=5172.5  F=775.9  **ap=4.0mm**  ae=0.64  MRR=1.99
  红线提示： RL-006 Ap=8.00mm > clamp(P=0.5D)=4.00mm，截断到 clamp 上限

两个问题叠在一起：
① 引擎原始给出的精加工 Ap 是 **8.00mm**（= 1.0×D8），这对精加工荒谬；
② clamp 只把它截到 0.5D = 4.00mm —— 仍然是精加工的几十倍。

而任务书 P1-03 明确规定的口径是：
  「精加工：depth_abs 0.1~0.3mm 不折算；Ae 不折」
也就是说精加工 Ap 应当由**绝对深度 0.1~0.3mm** 决定，而不是走粗加工的百分比×clamp 那条路。
这说明精加工分支的参数管线走错了（疑似把粗加工的 ap_pct 管线套用到了精加工）。

请完成：
1. 查清根因：是 SpeedFeedTable 的"精铣"预设里 ap_pct 被错填，还是引擎把
   operation="finishing" 走到了粗加工的 Ap 分支？两条路都要查，给出定位证据（文件:行号）。
2. 修正后必须满足：
   - 精加工 Ap 落在 0.1~0.3mm（按材料/刀具可在区间内取，但不许出区间）
   - 精加工 Ae 不折算（保持预设或按规则给定）
   - 粗加工 Ap/Ae 的行为**不得被这次修改影响**（回归）
3. 补测试用例到 D:\NX_AI\tests\test_cutting_rules.py：
   新增一条断言「任意材料 + 任意刀具 + operation=finishing → 0.1 ≤ ap_mm ≤ 0.3」，
   并保留"故意改坏规则必须失败"的元测试风格。
4. 重新跑一次真实零件（NX2412）验证，把修复前后的参数对照贴进报告。

验收标准：test_cutting_rules.py 新增断言通过且总数 >= 10；真实零件上 finishing 的 ap 落在 0.1~0.3mm。
```

---

### FIX-03 刀具库刃长数据触发长径比红线，需人工核实数据真伪

```text
【任务 FIX-03】核实刀具库刃长数据，或修正长径比判定的语义
【状态】**验收方已完成数据审计并给出语义结论**（`D:\NX_AI\tools\audit_tool_data.py`、
  `D:\NX_AI\状态\tool_data_audit.json`）。本卡剩"决策 + 落地"，**决策需要用户拍板**。
  审计事实（43 把刀全表已出）：
    * **34 / 43 把刀（79%）都会触发 RL-007**，从 φ0.5 钻头到 φ42 面铣无一幸免；
    * 刀具库里**没有任何悬伸字段**（无 overhang_mm / gauge_length_mm / stickout_mm）；
    * `flute_length_mm` 是**切削刃长度**，而 RL-007 把它当成**悬伸**算长径比。
  结论：**这是语义错误，不是（仅仅是）数据错误**。
    刃长是刀具规格，悬伸是装夹状态（同一把刀装深装浅，刃长都不变）。
    拿刃长/D 当刚性判据，等于把规格当装夹，必然大面积误报 —— 每份工序单都刷一堆红字，
    真出问题时反而没人看。
  需要用户决策（三选一，选完我或 Trae 都能落地）：
    A. 刀具/装夹数据里**补真实悬伸** overhang_mm（最正确，需要工艺员或刀具样本数据）
    B. RL-007 改判"**刃长比提示**"（info 级，文案改成"刃长/径 = x，仅作参考"），
       不再冒充刚性告警；刚性判定等有了悬伸数据再启用
    C. 维持现状但**把阈值按刃长口径重定**（例如刃长比 > 8 才提示）
  建议：**先做 B**（立刻消除 79% 的噪声、且不说假话），等有悬伸数据再升级到 A。

实测证据（同一把刀的真实数据来自 data/std/tools.json）：
  D12: flute_length_mm=50  → 长径比 50/12 = 4.17D  → 触发 RL-007 警告
  D8 : flute_length_mm=40  → 长径比 40/8  = 5.00D  → 触发 RL-007 警告

而 RL-007 的判定用的是「刃长 / 刀径」（规则库里的 overhang_ratio）。
问题在于这个口径本身有歧义：
  * 「悬伸」在车间里通常指**刀柄夹持后刀具伸出的总长度**，不是刃长；
  * 而标准 D12 平底立铣刀刃长一般 30mm 左右，50mm 属于加长刀（也可能确实是加长刀）。

请完成：
1. 明确 RL-007 的语义并写进规则库说明：到底判"刃长/刀径"还是"悬伸长度/刀径"。
   如果是后者，需要刀具数据里新增 `overhang_mm` 字段（当前 tools.json 没有），
   并说明缺失时的降级行为（例如：缺 overhang 就跳过 RL-007，而不是拿刃长硬套）。
2. 核实 tools.json 里 D12/D8 等前 10 把刀的 `flute_length_mm` 是否来自旧 XML 的真实值
   （旧库在 C:\NxCamToolkit\data\ToolLibrary.xml），把核实结果写进报告。
   如果旧数据本身不可靠，标注为"待用户确认"，**不要擅自改数**。
3. 给出结论：现在这条警告是"真该报"还是"误报"。两种情况都要有说服力。

验收标准：报告里明确 RL-007 的最终语义 + 缺失字段的降级行为 + D12/D8 数据的核实结论；
规则库 JSON 里该规则的说明字段被更新（数据驱动，不硬编码）。
```

---

## 附：本轮已验证通过的成果（不需要修，作为基线）

验收方在真实零件（后盖铝板-LQD80-镜像-175电机.prt，NX2412）上跑通了完整 L1 管线：

```
NX 真实模型 → model_facts.py 提工艺事实 → propose_plan.py 选刀+算参数
           → 8 条业务红线强制生效 → 生成工序单 → 脱敏(43 处) → 写入本地记忆(2→3 条)
```

对照文件：`D:\NX_AI\状态\model_facts.jsonl`、`D:\NX_AI\状态\artifacts\plan_20261016_180009\`、
`D:\NX_AI\docs\P2-00_端到端冒烟与API发现.md`。这两条链（自动编程建议 + 记忆闭环）后续**不要退化**。
