# P2 集成阶段任务卡 —— 从"零件"到"能跑的产品"

> 编制：2026-10-16（验收方实测后追加）
> 背景：P0/P1 共 19 张卡已产出 16 张，模块齐全、自测 51/51 全绿，但**还没有一次把模块串起来的端到端运行**。
> 验收方已先行验证了基础链路（见 P2-01 卡内的实测事实），本阶段目标：**让产品第一次真正跑起来**。

---

### P2-01 单入口命令 + 集成冒烟（真实零件）

```text
【任务 P2-01】把模块串成一个可运行的入口，并用真实零件跑通

验收方已经用你的 nx_launch.ps1 + 真实零件做过一次冒烟，实测事实如下（这是地面真相，按它写代码）：

| 观察 | NX2212 | NX2412 |
| run_journal + Python journal + 打开真实零件 | ✅ 成功 13–15 秒 | ✅ 成功 15 秒 |
| Python 版本 | 3.10.6 | 3.11.9 |
| part.Leaf | 后盖铝板-LQD80-镜像-175电机 | 同 |
| len(part.Bodies) | ❌ TypeError（集合没有 __len__） | ❌ 同 |
| part.Bodies.ToArray() | ❌ AttributeError | ❌ 同 |
| list(part.Bodies) / sum(1 for _ in part.Bodies) | ✅ 92 个 | ✅ 2 个 |
| repr(part.Units) | <class 'NXOpen.PartUnits'>（拿到的是类，不是值） | 同 |
| part.Units == PartUnits.Millimeters | False（说明访问器不对） | False |
| 全局环境变量 | 4 次运行全部零改动 | 同 |

**注意第 3 条**：同一零件两个版本报出 92 vs 2。这不是笔误，是必须查清的事实——
任何"按 NX 对象计数"的业务逻辑（BOM、工序数、电极数量、批量导出条目）都会因此产生错误结果。
本卡必须复现并给出解释（假设方向：NX2412 的 Parts.OpenActiveDisplay 是否只加载了部分实体/
是否进入了轻量化或只读加载/是否需要在打开后显式加载组件；不确定就标注"未证实"并给出验证方法）。

请完成：
1. 新建单入口 `D:\NX_AI\nxai.ps1`（PowerShell 5.1，UTF-8 with BOM），子命令：
   - `nxai list`                       → 调用 P0-02 的能力，列出本机所有 NX 版本（表格）
   - `nxai run -Version <2212|2412|latest> -Task <open-part|hello> [-Part <prt路径>]`
     流程：探测版本 → 构造进程级环境 → 通过 P1-01 任务引擎提交任务 →
     调用 P0-03 启动器执行对应 journal → 回收 stdout/stderr →
     产物归档到 `D:\NX_AI\状态\artifacts\<task_id>\` → 写 tasks.db → 返回正确退出码
   - `nxai verify -TaskId <id>`        → 校验该任务的产物完整性（文件存在 + 哈希）
2. 通过它跑真实零件两次（2212 与 2412 各一次），零件用：
   `C:\Users\Administrator\Desktop\UG插件\后盖铝板-LQD80-镜像-175电机.prt`
3. 写 `D:\NX_AI\docs\P2-01_集成冒烟报告.md`：
   - 两次运行的完整证据（命令行、耗时、退出码、产物清单）
   - **bodies 92 vs 2 的调查结论**（必须给出可复现的验证步骤，或明确写"未证实 + 下一步怎么验"）
   - nxai 单入口的使用说明与示例
4. 不许改 NX 安装目录、不许写全局环境变量（nx_launch.ps1 的 -AssertNoGlobalChange 必须开）。

验收标准：`nxai list` 列出两个版本；`nxai run` 在两个版本上各成功一次且产物落在 artifacts 下；
报告里对 92 vs 2 给出可验证的结论或明确的"未证实 + 验证方法"。
```

---

### P2-02 控制台与编码兼容修复

```text
【任务 P2-02】修复三个已实测 bug，并统一控制台编码

验收方实测到的三个真 bug：
1. 【会崩】`D:\NX_AI\core\bridge_v2_selftest.py:617` 打印 emoji 💥，在 GBK 控制台下抛
   UnicodeEncodeError: 'gbk' codec can't encode character '\U0001f4a5'，
   导致自测直接崩溃、退出码 1（实际 8/8 全过）。加 PYTHONIOENCODING=utf-8 后才正常。
   → 全项目排查同类问题：所有 print 到 stdout 的中文/符号，在 cp936 控制台下的行为。
   → 统一方案（二选一并说明理由）：
     a) 每个可执行脚本入口处 `sys.stdout.reconfigure(encoding='utf-8', errors='replace')`（Python 3.7+）
     b) 去掉所有非 GBK 可编码字符（emoji、部分符号）
   要求：修完后在**不设 PYTHONIOENCODING** 的默认控制台下，所有 --self-test / self-test 必须零异常。
2. 【功能坏】`D:\NX_AI\nx\verify_refs.ps1`：传 -Version all 不生效，
   路径拼成 `dist\\NxBridge.dll`。请修成支持 -Version <2212|2412|all>，
   并对每个 DLL 输出它实际引用的 NXOpen 程序集版本（用反射 GetReferencedAssemblies）。
3. 【语义不明】`D:\NX_AI\data\validate.py` 默认参数下返回退出码 1，但只有 7 条 info 级告警。
   请明确退出码语义（例如：0=无问题 1=有 error 2=仅有 warning/info）并在 --help 里写清楚。
   同时注意它报出的真实业务发现：旧 XML 里**丝锥的 Feed 语义是「螺距×转速」**，
   与铣/钻的 mm/min 不同 —— 这条必须写进校验规则说明。

验收标准：三个问题各自给出"修复前/修复后"的对照证据；默认控制台下自测零异常。
```

---

### P2-03 桌面主控接线（版本中心 + 任务中心）

```text
【任务 P2-03】把 P1-08 的界面骨架接上真实数据

P1-08 只做了骨架。现在接线：
1. 版本中心页：读 `D:\NX_AI\状态\versions.json`（或直接调 P0-02），
   显示每个版本的：版本号、安装路径、能力位（run_journal / managed / 内嵌 Python 版本 / CLR 类型）。
   提供"测试连接"按钮 → 实际跑一次最轻量的探测（例如 nxai run -Task hello），显示耗时与结果。
2. 任务中心页：读 P1-01 的 `D:\NX_AI\状态\tasks.db`，显示任务列表
   （id / 类型 / 目标版本 / 状态 / 尝试次数 / 耗时 / 产物路径），支持筛选与"重试失败任务"。
3. 日志页：显示当前选中任务的 stdout/stderr（来自 artifacts\<task_id>\）。
4. 记忆页（可选加分）：显示 P1-04 memory.db 的条数与最近 N 条，带"本地记忆不出机"开关。
5. 硬要求：没有 NX 的机器上也能启动（NX 相关初始化延迟到点击时）；长操作走后台线程，界面不卡。

验收标准：在一台没装 NX 的机器（或临时改名 NX 目录模拟）上界面能启动；
版本页能列出本机真实版本；任务页能列出 tasks.db 里的真实任务。
```

---

### P2-04 安装器替换（多版本一次装全 + 备份清单回滚）

```text
【任务 P2-04】用 P0-04 的 deploy.ps1 重写安装/卸载器，替换旧的 install.ps1

旧 install.ps1（C:\Users\Administrator\Desktop\UG插件制作\NxCamWizard_v1.6.1_20260831\）的 4 个高危问题：
① 卸载 `Remove-Item -Recurse -Force` 整目录、无备份无清单；
② 无条件清空全局唯一的 UGII_VENDOR_DIR（会让别家插件一起失效）；
③ 写系统级变量 + 永久写 UGII_LOCAL_USER_DEFAULTS 关闭 NXOpen 签名警告，卸载不移除；
④ 注册表探测写错键（找 Unigraphics Solutions\NX，实际是 Siemens\NX <数字>）+ 只扫 C:/D: 盘。

请用 P0-04 已完成的 deploy.ps1（纯函数模块：Install/Uninstall/Rollback/Verify + 清单 schema）
实现 `D:\NX_AI\installer\`：
1. `install.ps1`：探测**全部** NX 版本 → 列出让用户多选（支持"全部"）→ 逐版本按清单部署 → 写清单
2. `uninstall.ps1 -Manifest <路径>`：严格按清单反向执行；恢复环境变量时**只在当前值等于自己写入的值时**才恢复
3. `rollback.ps1 -Manifest <路径>`：按备份还原
4. 全程：-DryRun 支持、三态输出（新增/覆盖/跳过）、失败非零退出码、无备份不删除
5. `D:\NX_AI\docs\P2-04_安装器设计.md`：说明与旧 install.ps1 的逐条对比（4 个高危问题如何被消除）

验收标准：在一台**本机**上做完整往返（安装→Verify→卸载→Rollback），结束后目录与变量回到初始状态；
用 `D:\NX_AI\tools\test_deploy_roundtrip.ps1` 同样的思路做自动化断言并贴输出。
```

---

### P2-05 NX Open 跨版本兼容层

```text
【任务 P2-05】封装"跨版本安全"的 NX Open 访问层（本卡是反复踩坑的根因治理）

验收方实测证明 NX Open 的 Python 绑定在两个版本上行为不同，直接写会踩坑：
- `len(collection)` → TypeError（集合不实现 __len__）
- `collection.ToArray()` → AttributeError（没有这个方法）
- 唯一可移植的方式：**迭代**（`list(coll)` / `sum(1 for _ in coll)`）
- `part.Units` → 返回的是**类对象** `NXOpen.PartUnits`，不是枚举值；
  `part.Units == NXOpen.PartUnits.Millimeters` 恒为 False
- 同一零件 `list(part.Bodies)` 在 2212 报 92、2412 报 2

请完成：
1. `D:\NX_AI\nx\compat.py`：一个可被 journal 直接 import 的兼容层，提供：
   - `col_count(coll)` / `col_list(coll)`：跨版本安全计数与取值（内部只用迭代，带异常兜底与明确的错误信息）
   - `part_units(part)`：用**多路径**取单位（尝试 part.Units、part.PartUnits 等，逐个验证是否返回枚举值），
     取不到时返回明确标记而不是假值
   - `nx_version()`：从 `UGII_BASE_DIR` 取版本标签
   - `open_part(session, path)`：带清晰错误分类的打开（文件不存在 / 版本不兼容 / 权限 / 未知），
     区分"部件由更高版本保存"这一常见失败
   - `log_line(path, text)`：UTF-8 追加写产物（跨版本一致）
2. `D:\NX_AI\nx\probe\compat_probe.py`：用上面的兼容层重写探针，在 2212 与 2412 上各跑一次，
   输出结构化结果（JSON 单行）到 `D:\NX_AI\状态\compat_probe.jsonl`
3. `D:\NX_AI\nx\docs\NXOpen跨版本兼容矩阵.md`：把两个版本上每种 API 的实测行为列成矩阵
   （可用/不可用/行为差异），并给出"该怎么写"的结论。**未证实的必须标注"未证实"。**
4. 回归：`D:\NX_AI\tests\test_compat.py` 能在没有 NX 的环境下用假对象测试兼容层的逻辑分支。

验收标准：兼容层在两个真实 NX 版本上各跑通一次、输出 JSONL；
兼容矩阵里每个结论都有本次实测的支撑；拿掉兼容层改用 `len()` 会失败的对照证据也一并给出。
```

---

## 附：本阶段的先后顺序建议

```
P2-02（修 bug，快，1 张卡）
   ↓
P2-01（单入口 + 集成冒烟，最关键）
   ↓
P2-05（兼容层，把踩坑根治）
   ↓
P2-04（安装器替换，面向交付）
   ↓
P2-03（界面接线，最后做）
```
