diff --git a/APS系统操作手册.docx b/APS系统操作手册.docx new file mode 100644 index 0000000..3aac1dc Binary files /dev/null and b/APS系统操作手册.docx differ diff --git a/APS系统操作手册.md b/APS系统操作手册.md new file mode 100644 index 0000000..f2cca91 --- /dev/null +++ b/APS系统操作手册.md @@ -0,0 +1,376 @@ +# 工业智核 APS 计划排产智能体 · 操作手册 + +> 版本:2026-09-01(对齐当前代码能力,含 round-65~67、DXF-01、FF-01 功能开关) +> 适用对象:计划员、实施顾问、演示/验收人员 +> 相关文档:`docs/product/features.md`(功能清单)、`docs/product/journeys.md`(业务旅程)、`锐扬演示-操作手册.md`(锐扬演示剧本)、`docs/product/flex-simulation-guide.md`(柔性排产专项手册) + +--- + +## 1. 系统是什么 + +左手**自然语言对话**、右手**可视化排产工作台**的计划排产智能体。核心链路: + +``` +销售订单 → MRP 分解(自制 / 采购 / 委外)→ 多策略试排(规则/CP/GA/NSGA-II) + → 方案对比 → P2 确认发布 → MES 下发 → 报工回流 +``` + +两大排产轨道: + +| 轨道 | 模型 | 适用场景 | +| --- | --- | --- | +| **固定轨** | 产线选线 + 工位占槽(RuleEngine / CP / GA / HYBRID) | 产线固定的常规离散制造 | +| **柔性轨** | 能力池 + 虚拟产线(PoolEngine) | 多品种小批量、设备可移动(如康尼芜湖线束/PDU 共线) | + +安全原则:一切写操作走**门禁确认卡**(P2 单确认 / P3 二次确认),执行前自动建检查点,全程审计链记账。试排只产出 DRAFT,不会自动发布;回滚前自动再存一次档。 + +--- + +## 2. 两种形态 + +| 形态 | 数据位置 | 启动命令 | 账号 | +| --- | --- | --- | --- | +| **Web** | 仓库 `server/data/`(或 `APS_HOME`) | `npm run dev:web` → 浏览器 http://localhost:5173 (API :8000) | JMS 企业账号登录(`.env` 配置,见 §3.2) | +| **桌面** | `C:\Users\<用户>\.aps`(与开发环境互不影响) | `npm run dev:desktop`(开发)/ 安装 `apps/desktop/release/aps-agent-desktop-0.1.0.exe` | 免账号,用授权码(见 §4) | + +--- + +## 3. 安装与启动 + +### 3.1 依赖安装(一次性) + +```powershell +cd D:\ItemSpace\14.工业智核\aps-agent +python -m venv .venv +.venv\Scripts\activate +pip install -r requirements.txt +copy .env.example .env # 可选,按需修改 +npm run bootstrap # 安装 apps/web + apps/desktop 依赖 +``` + +### 3.2 Web 端登录配置(`.env`) + +```ini +APS_AUTH_PROVIDER=jms +JMS_AUTH_BASE_URL=https://jms-mes-api.jzplat.com +JMS_AUTH_SESSION_SECRET=至少32个随机字符 +APS_LICENSE_PROVIDER=mock +# 可选:限制单一企业 +# JMS_AUTH_TENANT_CODE=xxx +# JMS_AUTH_TENANT_NAME=xxx +``` + +> JMS 没有自助注册/找回密码,账号创建与密码重置由企业管理员处理。 + +### 3.3 启动 + +```powershell +npm run dev:web # Web:前后端一起起 +npm run dev:desktop # 桌面:Electron 窗口 +``` + +只起后端 API:`npm run dev:server`(http://localhost:8000,OpenAPI 文档 /docs)。 + +### 3.4 打包 + +```powershell +npm run build:web # 静态前端 → apps/web/dist +npm run build:desktop # Electron 安装包 → apps/desktop/release +``` + +--- + +## 4. 登录与授权 + +- **Web**:打开页面 → 选择所属企业(从 JMS 读取启用企业列表)→ 输入真实账号密码登录。 +- **桌面**:输入授权码,开发用演示码: + `APS-HOUR-DEMO`、`APS-DAY-DEMO`、`APS-WEEK-DEMO`、`APS-MONTH-DEMO`、`APS-YEAR-DEMO`、`APS-PERP-DEMO`。 + +--- + +## 5. 界面导览 + +``` +┌──────────────────────────────────────────────────────┐ +│ 顶部菜单栏(按功能开关过滤) │ +├──────────┬─────────────────────────┬─────────────────┤ +│ 左侧栏 │ 中部:对话区 / 工作台 │ 右侧工具导轨 │ +│ · 项目/任务│ 左说右动:说一句话, │ · 多智能体编排 │ +│ · 快捷入口 │ 右侧视图立即刷新 │ · DXF 图纸 │ +│ · 订单 │ │ · 柔性工作台 │ +│ · 主数据 │ │ · 知识库/治理台等 │ +└──────────┴─────────────────────────┴─────────────────┘ +``` + +- **Ctrl+K** 命令面板:快速跳转任意面板/视图(条目随功能开关过滤)。 +- 对话块里会插入**方案卡、确认卡、知识引用、报告**等富块,可直接点按交互。 +- 右侧视口:甘特(固定/柔性)/ 负荷 / 交期 / 分桶计划 / 利用率 / 对比表 / KPI 仪表盘 / 产能池 / 时间线。 + +--- + +## 6. 第一步:准备数据 + +### 6.1 工程目录导入(推荐) + +左侧「新建任务 / 新建项目」→ 选择工程目录(目录内放 Excel/CSV:订单、物料、BOM、工艺、设备、班历等),然后在对话框输入: + +``` +分析一下数据文件 +``` + +系统做项目深度分析(订单/物料/工艺/设备/BOM 计数 + 诊断),并自动导入世界。诊断口径:有效 / blocking(阻断)/ 跳过,blocking 会定位到具体文件具体行。 + +### 6.2 Excel/CSV 导入管线 + +主数据面板或订单面板的「导入」按钮 → `preview` 预览(表头映射 + 校验)→ P2 确认落库。支持:订单 / 物料 / BOM / 工艺路线 / 设备 / 模具 / 工序 / 区域。 + +### 6.3 DXF 工程图纸识别 + +右侧导轨「图纸」图标 → 上传 DXF / 填本地路径 / 扫描工程目录: +1. 自动解析标题栏(图号、名称、比例、版本,带字段级置信度徽标); +2. SVG 预览(滚轮缩放、拖拽平移、图层开关); +3. 物料候选表单人工核对 → 生成 P2 确认卡 → 批准后写入主数据。 + +> 图纸未承载的字段(材质、工时、外协、BOM 子件数量等)一律显式标 missing,绝不编造,必须人工确认。 + +### 6.4 康尼真实单(柔性演示) + +```powershell +.venv\Scripts\python.exe scripts\load_kangni.py +``` + +或对话口令「导入完整生产路线」,替换演示 flex* 数据后再跑柔性排产。 + +### 6.5 主数据录入顺序(铁律) + +**先工厂/产线 → 再物料 → 再 BOM → 再工序 → 再工艺路线 → 再产线-产品绑定 → 再订单**。缺一环,后续排产必然失败或"找不到依据"。 + +--- + +## 7. 主数据维护(侧栏「主数据」) + +页签与能力: + +| 页签 | 能做什么 | 权级 | +| --- | --- | --- | +| 资源 | 工厂→车间→产线→工位→设备树;产线编辑/停启用(停用不进新排产) | 编辑 P2 | +| 物料与 BOM | 库存/在途/安全库存/前置期编辑;BOM 明细用量、关键料编辑 | P2 | +| 日历与维保 | 班次/日历只读;维保窗口新增/取消(联动设备避让) | P2 | +| 工艺 | 工艺步骤工时 / 外协标记编辑 | P2 | +| 换型 | 产品族×产品族换型矩阵维护(口令「查看换型矩阵」) | P2 | +| 柔性资源 | 设备虚拟档案(能力集合、可移动/移动耗时、当前位置、工装适配)、能力池摘要 | 只读 + Excel 导入 | + +注意:所有主数据修改**只影响之后的新排产版本**,历史版本不回写;执行前自动建检查点、写审计链。 + +--- + +## 8. 订单管理(左侧栏「订单」) + +### 8.1 销售订单 + +- 列表 + 明细:新增 / 编辑 / 取消 / 完成,全部走 P2 确认卡。 +- `CANCELLED` / `COMPLETED` 订单不进新排产版本。 +- 状态机:仅 `APPROVED` 订单进排产。 + +### 8.2 订单池与审核 + +泳道看板:提交 / 批准 / 驳回 / 批量批准。口令:「订单池」「提交审核」「批量批准」。 + +### 8.3 预测订单 + +预测台账默认**不进**正式排产;可「预测纳入试排」做 what-if,确认后「预测转正」为正式 APPROVED 销售单。口令:「查看预测」「新建预测」「预测纳入试排」「预测转正」。 + +### 8.4 紧急插单 + +订单面板「插单」向导,或口令「插单快评」→ 查看影响评估 →「采用插单」(固定轨走 rush.evaluate/apply,柔性轨走 flex.rush;超扰动阈值自动升级为全量重排并出 P2 确认)。 + +### 8.5 订单追溯 + +任一订单可一键追溯完整链条:订单本体 → 主数据依据(BOM/工艺/库存)→ MRP 分解结果 → 排产版本与工单 → 缺失项(无 BOM / 无路线 / 未绑产线 / 库存不足)。柔性轨另走 FO→BOM→虚拟产线→工单→设备负荷,界面标明当前走哪条链。 + +--- + +## 9. MRP 订单分解 + +入口:订单面板「分解建议」/「分解此单」,或口令「分解订单 SO-001」「执行MRP分解」「生成采购建议」。 + +| 分支 | 依据 | 产出 | +| --- | --- | --- | +| 自制 | 订单明细成品 + 工艺路线 | 自制建议(交排产生成 PO/WO) | +| 采购 | BOM 净需求 = 毛需求 − 库存 − 在途 − 已下达供应 | 采购建议 DRAFT(数量 + 建议下单日按前置期倒排) | +| 委外 | 工艺步骤 `isExternal` 标记 | 委外建议 DRAFT | + +- 分解是 **P1**:只写建议草稿,幂等替换,可重复执行。 +- 下达:口令「下达MRP建议单」→ **P2** 确认卡 → 批准 → DRAFT 转 RELEASED(自动补预计到料日,供齐套检查使用)。 +- 多层 BOM 完整展开:半成品缺工艺不会伪装成采购,缺工艺的条目会明示。 + +--- + +## 10. 排产 + +### 10.1 固定轨试排 + +口令示例:「试排一版交期优先」「按产能均衡排一下」「换型最小化试排」「战役合并试排」。 + +- 策略模板:`DELIVERY_FIRST`(交期优先)/ `FIFO` / `CAPACITY_BALANCE` / `CHANGEOVER_MIN`(换型最小化)/ `COST_FIRST` / `COMPREHENSIVE` / `CAMPAIGN`(批次合并)。 +- 引擎:RULE / CP-SAT(工序级+Cumulative)/ GA / NSGA-II(三目标 Pareto)/ HYBRID。 +- 滚动窗口:realtime60m / short2h / mid2d / long7d 多级时域。 +- 产出 **DRAFT** 版本 + 冲突清单,右侧甘特/负荷/交期视图即时刷新。 + +### 10.2 柔性轨排产(能力池 + 虚拟产线) + +口令「跑一版柔性排产」,或右侧「柔性工作台」点选;接口 `POST /api/flex/schedule`: + +| 参数 | 取值 | 含义 | +| --- | --- | --- | +| `sortMode` | `ASC` / `DESC` / `BOTTLENECK` | 正排 / 倒排 / 瓶颈锚(默认) | +| `orderIds` | 订单 id 数组 | 只排指定订单 | + +系统按订单工艺路线逐工序从能力池动态取设备,拼出虚拟产线(含模具换型与设备移动耗时),用完即散。柔性甘特可切「虚拟线预演」,支持正排/倒排/瓶颈锚**三模式对比**。 + +### 10.3 方案对比与采用 + +口令「对比几种策略」→ 沙盒并行多策略试排 → 返回方案卡(KPI / 差异 / Pareto + 偏好推荐)→ 点「采用此方案」落到主干草稿。**对比 ≠ 发布,主干世界在采用前不变。** + +### 10.4 发布 / 重置 / 回滚 + +| 动作 | 口令 | 权级 | 说明 | +| --- | --- | --- | --- | +| 发布 | 「发布这个版本」 | P2 | DRAFT → PUBLISHED,执行前自动建档;只有完整 DRAFT 才能发布,证据一致的已发布版本才能进 MES | +| 存档 | 「存个档」 | P1 | 对话上下文 + 世界状态成对快照 | +| 回滚 | 点时间线检查点 / 「回滚」 | P2 | 回滚前自动再存一次档(安全网) | +| 重置 | 「清空数据重新初始化」 | P2 | 重播演示种子(电子装配演示工厂) | + +### 10.5 工单拖拽调程 + +固定/柔性甘特均可直接拖动工单:15 分钟吸附、边拖边校验、松手生成 P2 确认卡,批准后形成新版本。 + +### 10.6 冲突处理 + +冲突中心按类型给出修复建议:安全修复 P1 一键应用;涉及重排的出 P2 确认卡。硬阻断(如缺料、无可用设备)会显示聚合计数、具体对象、证据和修复入口,**不会**显示绿色"排产完成"。 + +--- + +## 11. 计划层(Advanced Planning) + +| 能力 | 口令 | 说明 | +| --- | --- | --- | +| 时间分桶计划 | 「时间分桶」「混合分桶」 | 近 7 日 / 中 4 周 / 远月混合时段 | +| 粗能力评估 | 「有限产能粗评」「无限产能粗评」「粗能力对比」 | FINITE / INFINITE 双模式对照 | +| 可行性分析 | 「可行性分析」「能否按期」 | 累计粗能力 vs 交期需求,定位缺口桶与订单 slack | +| 库存投影 | 「库存投影」「库存投影 CTRL-A」 | 期初+在途+毛需求+到货的日/周库存曲线,断料/安全库存告警 | +| 产能平衡 | 「削峰」「产能平衡」 | 超载桶→空档挪动建议,前后负荷对照 | +| 产供决策 | 「产供决策」「加班建议」 | 残留缺口→加班→扩线→外协结构化建议 | +| 交期承诺 | 对话询价 / 柔性工作台询价 | 询单插入 → 乐观/预计/悲观三区间 + 资源缺口提示 | +| 瓶颈产能 | 视口「产能池」 | 日产能=瓶颈工序可用工时÷单件工时;设备停机/调区即时重算,含 4h/1d/1w 预测与 85/95 预警 | + +--- + +## 12. 动态调度(异常分级重排) + +| 级别 | 触发 | 行为 | +| --- | --- | --- | +| L1 | 设备故障(优先) | 池内备机换机,最小扰动 | +| L2 | 短窗变化 | 窗内重排,P2 确认 | +| L3 | 日级变化 | 日窗重排,P2 确认 | +| L4 | 重大异常 | 全局重排,P2 确认 | + +典型演示:可移动压接机坏一台 → 池有冗余、交期几乎不受影响;单一瓶颈设备(如激光焊接)故障 → 立即报「无可用设备」,提示备机/外包/重议交期。WMS 缺料事件 → 重排 → 确认 → MES 下发 → 回执全链路闭环。 + +--- + +## 13. 结果视图与报表 + +| 视口 | 内容 | +| --- | --- | +| 甘特 | 固定/柔性双轨,工单拖拽、冲突高亮 | +| 负荷 / 利用率 | 产线/设备负荷、柔性设备 7 日热力、班组并发触顶 | +| 交期看板 | 订单准时/延误一览 | +| 对比表 | 固定策略 + 柔性三模式 KPI 矩阵,最优格高亮与 delta | +| KPI 仪表盘 | 利用率/准时率/在制/延误汇总(口令「打开仪表盘」) | +| 产能池 | 池负载率、瓶颈色标、短中长期预测、设备调配 What-if | +| 时间线 | 检查点列表,点击回滚 | + +报表:口令「生成日报」「版本对比」→ 数字来自冻结快照模板,不经 LLM 改写。审计支持哈希链校验、Merkle 树、断链检测与归档导出。 + +--- + +## 14. MES 下发与报工回流 + +1. 已发布版本 → 「MES 执行」(柔性工作台)或下发口令 → **P3** 二次确认 → Mock MES 幂等下发。 +2. 报工回流:`mes.report`(P1)回报进度/完工 → 订单整单完工状态联动推进。 +3. 订单页阶段状态来自持久化的闭环问题与 MES 事实,刷新后不退回静态"待确认"。 + +--- + +## 15. 多智能体编排(右侧栏「多智能体编排」) + +1. **创建智能体**:输入名字、选角色(数据解析 / MRP 分解 / 柔性排产 / 报告…)。 +2. **挂 Goal**:填标题 → 选模板(如「解析→分解→下达→排产→报告」5 任务)→ 看门狗开始监控。 +3. **分发执行**:看门狗按能力边界分发给空闲智能体,没有合适的就**自动新建**。 +4. **消息总线**:实时显示任务接力;也可手动给任意智能体发消息,对方回报自身状态与能力边界。 +5. **看门狗**:RUNNING 超期(默认 45s,可一键调 10s 演示)→ 收回重排 → 重试耗尽升级 ATTENTION;全部完成自动结案 ACHIEVED。任务失败诚实落 FAILED 并升级,不会假装成功。 +6. 重复演示前先点一次「恢复」(或 `POST /api/mesh/reset`)。 + +--- + +## 16. 对话口令速查 + +| 类别 | 口令 | +| --- | --- | +| 数据 | 分析一下数据文件 / 导入完整生产路线 / 清空数据重新初始化 | +| MRP | 执行MRP分解 / 分解订单 SO-001 / 生成采购建议 / 下达MRP建议单 | +| 排产 | 试排一版交期优先 / 按产能均衡排一下 / 换型最小化试排 / 战役合并试排 / 跑一版柔性排产 / 三模式对比 | +| 方案 | 对比几种策略 / 采用此方案 / 发布这个版本 | +| 插单 | 插单快评 / 采用插单 | +| 计划层 | 时间分桶 / 可行性分析 / 能否按期 / 库存投影 / 削峰 / 产供决策 / 加班建议 | +| 订单 | 订单池 / 提交审核 / 批量批准 / 查看预测 / 新建预测 / 预测纳入试排 / 预测转正 | +| 主数据 | 查看换型矩阵 / 提高VIP权重到10 | +| 知识 | 换线有什么规定 / @知识:标题 | +| 报告 | 生成日报 / 版本对比 / 打开仪表盘 | +| 存档 | 存个档 / 回滚 | + +> 意图管线为「正则快路 → LLM JSON」两级;LLM 超时或非法输出自动降级正则,知识未命中会诚实说没有,不编造 SOP。 + +--- + +## 17. 治理与安全(设置 → 治理台) + +- **门禁分级**:P0 只读 / P1 写草稿直通 / P2 单确认卡 / P3 外部副作用二次确认(当前用于 MES 下发);审批角色可按动作分级。 +- **审计链**:每次执行记账,哈希链可校验,支持断链检测(治理台 ⚠ 徽标)、归档加密与导出。 +- **约束配置中心**:13 类约束目录,可配启停与硬/软;硬约束拦截发布。 +- **行业规则包**:SOP → 排产约束编译,预览后 P2 应用。 +- **唯一执行通道**:所有写操作最终都走 `/api/actions/confirm`,不存在绕过确认卡的"直接执行"接口。 + +### 功能开关(FF-01) + +`server/data/features.json` 控制 12 个功能键(visual/orders/master/flex/timeline/drawing/mesh/knowledge/skills/gov/regen/settings)的界面可见性。改文件刷新浏览器即生效,无需重启;文件缺失/损坏时全部默认开启(fail-open)。注意:开关只隐藏入口,不替代 Harness 门禁。 + +--- + +## 18. 常见问题 + +| 问题 | 处理 | +| --- | --- | +| 排产失败提示缺失项 | 按 §6.5 顺序检查主数据:产线绑定 → BOM → 工艺路线 → 库存;用订单「追溯」看具体缺什么 | +| 排产被硬阻断(缺料/无设备) | 看冲突中心的聚合计数与证据:缺料先下达采购建议;无设备查设备停用/维保/故障状态 | +| 试排后想撤回 | 时间线点上一个检查点回滚(P2),回滚前系统自动再存档 | +| 三模式对比结果一样 | 产能宽裕时趋同;压缩交期制造压力后才分化 | +| 删了前端"项目"数据还在 | 项目/任务在前端工作区,排产世界在后端;清世界用「清空数据重新初始化」 | +| Web 登录不了 | 检查 `.env` 的 JMS 配置与会话密钥;账号问题找企业管理员 | +| 想看接口 | 后端起服后访问 http://localhost:8000/docs | +| 一键演示柔性全流程 | `.venv\Scripts\python.exe scripts\flex_simulation.py`(纯内存、结果确定,不起服务) | + +### 验收快速路径 + +1. 「分析一下数据文件」→ 工程导入成功; +2. 「执行MRP分解」→ 采购建议出现;「下达MRP建议单」→ 批准后转 RELEASED; +3. 「对比几种策略」→ 方案卡 → 采用 → 右侧甘特刷新; +4. 「存个档」→ 点检查点回滚(P2 确认); +5. 「跑一版柔性排产」→ 虚拟产线 / 工单出现; +6. 「发布这个版本」→ P2 批准 → 版本 PUBLISHED; +7. 回归测试:`pytest tests/golden -q`(当前基线 1071 passed / 1 既有失败 `test_preference_features`,与业务无关)。 + +--- + +*维护:能力变化以 `docs/product/features.md` 与 `docs/CHANGELOG.md` 为准,本手册随版本同步更新。* diff --git a/_shots/01-登录.png b/_shots/01-登录.png new file mode 100644 index 0000000..d128e47 Binary files /dev/null and b/_shots/01-登录.png differ diff --git a/_shots/02-主界面.png b/_shots/02-主界面.png new file mode 100644 index 0000000..9628e88 Binary files /dev/null and b/_shots/02-主界面.png differ diff --git a/_shots/03-对话柔性排产.png b/_shots/03-对话柔性排产.png new file mode 100644 index 0000000..1d6fbf0 Binary files /dev/null and b/_shots/03-对话柔性排产.png differ diff --git a/_shots/07-分桶计划.png b/_shots/07-分桶计划.png new file mode 100644 index 0000000..03515ce Binary files /dev/null and b/_shots/07-分桶计划.png differ diff --git a/_shots/08-KPI仪表盘.png b/_shots/08-KPI仪表盘.png new file mode 100644 index 0000000..ad287ea Binary files /dev/null and b/_shots/08-KPI仪表盘.png differ diff --git a/_shots/09-产能池.png b/_shots/09-产能池.png new file mode 100644 index 0000000..2636c18 Binary files /dev/null and b/_shots/09-产能池.png differ diff --git a/_shots/09-对比表.png b/_shots/09-对比表.png new file mode 100644 index 0000000..0b13cd9 Binary files /dev/null and b/_shots/09-对比表.png differ diff --git a/_shots/10-产能池.png b/_shots/10-产能池.png new file mode 100644 index 0000000..be9c92c Binary files /dev/null and b/_shots/10-产能池.png differ diff --git a/_shots/12-柔性工作台.png b/_shots/12-柔性工作台.png new file mode 100644 index 0000000..a990d78 Binary files /dev/null and b/_shots/12-柔性工作台.png differ diff --git a/_shots/13-订单管理.png b/_shots/13-订单管理.png new file mode 100644 index 0000000..febce67 Binary files /dev/null and b/_shots/13-订单管理.png differ diff --git a/_shots/14-主数据.png b/_shots/14-主数据.png new file mode 100644 index 0000000..eb80180 Binary files /dev/null and b/_shots/14-主数据.png differ diff --git a/_shots/15-排产结构.png b/_shots/15-排产结构.png new file mode 100644 index 0000000..6a1fb7d Binary files /dev/null and b/_shots/15-排产结构.png differ diff --git a/_shots/16-多智能体编排.png b/_shots/16-多智能体编排.png new file mode 100644 index 0000000..fd88215 Binary files /dev/null and b/_shots/16-多智能体编排.png differ diff --git a/_shots/17-图纸识别.png b/_shots/17-图纸识别.png new file mode 100644 index 0000000..6df168a Binary files /dev/null and b/_shots/17-图纸识别.png differ diff --git a/_shots/18-命令面板.png b/_shots/18-命令面板.png new file mode 100644 index 0000000..ce3a4a8 Binary files /dev/null and b/_shots/18-命令面板.png differ diff --git a/_shots/19-对话策略对比.png b/_shots/19-对话策略对比.png new file mode 100644 index 0000000..28dfc0a Binary files /dev/null and b/_shots/19-对话策略对比.png differ diff --git a/_shots/20-对话知识问答.png b/_shots/20-对话知识问答.png new file mode 100644 index 0000000..05a0d5e Binary files /dev/null and b/_shots/20-对话知识问答.png differ diff --git a/_shots/22-世界对比.png b/_shots/22-世界对比.png new file mode 100644 index 0000000..859db3e Binary files /dev/null and b/_shots/22-世界对比.png differ diff --git a/_shots/23-利用率.png b/_shots/23-利用率.png new file mode 100644 index 0000000..316a0d3 Binary files /dev/null and b/_shots/23-利用率.png differ diff --git a/_shots/capture.mjs b/_shots/capture.mjs new file mode 100644 index 0000000..82d52bc --- /dev/null +++ b/_shots/capture.mjs @@ -0,0 +1,198 @@ +// ============================================================ +// 操作手册截图自动化 v2:全新演示种子数据(不复制现场 world) +// 隔离后端(8100) + Vite(5399) + Playwright,结束杀子进程。 +// ============================================================ +import { spawn } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { createRequire } from 'node:module'; + +const ROOT = path.resolve(import.meta.dirname, '..'); +const require = createRequire(path.join(ROOT, 'apps', 'web', 'package.json')); +const { chromium } = require('playwright'); + +const SHOTS = path.join(ROOT, '_shots'); +const TMP = path.join(SHOTS, 'tmp2'); +fs.mkdirSync(SHOTS, { recursive: true }); +fs.rmSync(TMP, { recursive: true, force: true }); +fs.mkdirSync(TMP, { recursive: true }); + +const PY = path.join(ROOT, '.venv', 'Scripts', 'python.exe'); +const VITE = path.join(ROOT, 'apps', 'web', 'node_modules', 'vite', 'bin', 'vite.js'); +const API = 'http://127.0.0.1:8100'; +const UI = 'http://localhost:5399'; + +// 只带知识与配置,世界/主库走全新演示种子 +for (const f of ['knowledge.json', 'config.json']) { + const src = path.join(ROOT, 'server', 'data', f); + if (fs.existsSync(src)) fs.copyFileSync(src, path.join(TMP, f)); +} + +const children = []; +function run(name, cmd, args, opts = {}) { + const log = fs.createWriteStream(path.join(SHOTS, `${name}2.log`)); + const p = spawn(cmd, args, { cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'], ...opts }); + p.stdout.pipe(log); p.stderr.pipe(log); + children.push(p); + return p; +} +function cleanup() { + for (const p of children) { + try { spawn('taskkill', ['/PID', String(p.pid), '/T', '/F'], { stdio: 'ignore' }); } catch {} + } +} +process.on('exit', cleanup); + +async function waitHttp(url, timeoutMs) { + const t0 = Date.now(); + while (Date.now() - t0 < timeoutMs) { + try { const r = await fetch(url); if (r.status < 500) return; } catch {} + await new Promise(r => setTimeout(r, 1000)); + } + throw new Error('waitHttp timeout: ' + url); +} + +run('backend', PY, ['-m', 'uvicorn', 'server.main:app', '--port', '8100'], { + env: { + ...process.env, + APS_PORT: '8100', + APS_HOME: TMP, + APS_WORLD_PATH: path.join(TMP, 'world.json'), + APS_DB_PATH: path.join(TMP, 'master.db'), + APS_APPROVAL_PATH: path.join(TMP, 'approvals.json'), + APS_KNOWLEDGE_PATH: path.join(TMP, 'knowledge.json'), + APS_WEB_DEMO_SEED: '1', + }, +}); +run('frontend', process.execPath, [VITE, '--port', '5399', '--strictPort'], { + cwd: path.join(ROOT, 'apps', 'web'), + env: { ...process.env, VITE_API_TARGET: API }, +}); + +console.log('等待服务就绪…'); +await waitHttp(API + '/api/health', 120_000); +await waitHttp(UI, 120_000); +console.log('服务就绪'); + +const browser = await chromium.launch(); +const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1.5 }); +const results = []; + +async function shot(name) { + await page.waitForTimeout(700); + await page.screenshot({ path: path.join(SHOTS, name + '.png') }); + results.push(name); + console.log(' ✓', name); +} +async function tryStep(name, fn, timeoutMs = 12_000) { + try { await Promise.race([fn(), new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), timeoutMs))]); return true; } + catch (e) { console.log(' ✗ 跳过', name, '-', e.message.slice(0, 60)); return false; } +} + +// 01 登录弹窗 +await page.goto(UI); +await page.locator('.guest-login-trigger').waitFor({ state: 'visible', timeout: 60_000 }); +await page.locator('.guest-login-trigger').click(); +const dialog = page.locator('.auth-login-dialog'); +await dialog.waitFor({ state: 'visible' }); +await shot('01-登录'); +await dialog.locator('input[placeholder="请输入企业名称"]').fill('platform'); +await dialog.locator('input[placeholder="请输入用户名"]').fill('zhangzhen'); +await dialog.locator('input[placeholder="请输入密码"]').fill('sz@zz@1116'); +await dialog.locator('.login-submit').click(); +const composer = page.locator('textarea[placeholder^="输入排产指令"]'); +await composer.waitFor({ state: 'visible', timeout: 60_000 }); +await tryStep('主界面稳定', async () => { + await page.locator('text=快捷指令').first().waitFor({ state: 'visible', timeout: 40_000 }); +}, 45_000); +await page.waitForTimeout(2000); +await shot('02-主界面'); + +// 03 对话:固定轨试排(交期优先)→ 甘特有数据 +await composer.fill('试排一版交期优先'); +await composer.press('Enter'); +await tryStep('试排结果', async () => { + await page.locator('.msg.agent .bubble', { hasText: /排产|版本|工单/ }).last() + .waitFor({ state: 'visible', timeout: 150_000 }); +}, 160_000); +await page.waitForTimeout(2500); +await shot('03-对话试排'); + +// 视口页签(固定轨视图) +const resultBtn = page.locator('.right-rail button[data-tip="会话结果"], .right-rail button[data-tip="再点收起"]').first(); +if (!(await page.locator('.vp-toolbar').isVisible().catch(() => false))) { + await tryStep('打开会话结果', () => resultBtn.click()); +} +await page.locator('.vp-toolbar').waitFor({ state: 'visible', timeout: 20_000 }).catch(() => {}); +const tab = async (label, name) => { + const ok = await tryStep('页签:' + label, async () => { + await page.locator('.seg .tab', { hasText: label }).first().click(); + await page.waitForTimeout(1200); + }); + if (ok) await shot(name); +}; +await tab('甘特图', '04-甘特图'); +await tab('负荷热力', '05-负荷热力'); +await tab('交期承诺', '06-交期承诺'); + +// 07 对话:柔性排产 → 柔性视图有数据 +await tryStep('柔性排产', async () => { + await composer.fill('跑一版柔性排产'); + await composer.press('Enter'); + await page.locator('.flex-block-title', { hasText: '柔性排产' }).first() + .waitFor({ state: 'visible', timeout: 150_000 }); +}, 160_000); +await page.waitForTimeout(1500); +await shot('07-对话柔性排产'); + +// 柔性相关视口页签 +if (!(await page.locator('.vp-toolbar').isVisible().catch(() => false))) { + await tryStep('再开会话结果', () => resultBtn.click()); +} +await tab('柔性甘特', '08-柔性甘特'); +await tab('产能池', '09-产能池'); +await tab('分桶计划', '10-分桶计划'); +await tab('KPI', '11-KPI仪表盘'); +await tab('对比表', '12-对比表'); + +// 右侧工具面板 +const rail = async (tip, name, waitSel) => { + const ok = await tryStep('面板:' + tip, async () => { + await page.locator(`.right-rail button[data-tip="${tip}"]`).click(); + if (waitSel) await page.locator(waitSel).waitFor({ state: 'visible', timeout: 12_000 }); + await page.waitForTimeout(1000); + }); + if (ok) await shot(name); + await tryStep('收起', () => page.locator('.right-rail button[data-tip="再点收起"]').click(), 4_000); +}; +await rail('柔性工作台', '13-柔性工作台', '.flex-bench'); +await rail('订单管理', '14-订单管理'); +await rail('主数据', '15-主数据'); +await rail('排产结构', '16-排产结构'); +await rail('多智能体编排', '17-多智能体编排'); +await rail('图纸识别', '18-图纸识别'); + +// 19 命令面板 +await page.keyboard.press('Control+K'); +await tryStep('命令面板', async () => { + await page.locator('.palette input').waitFor({ state: 'visible', timeout: 8_000 }); + await page.locator('.palette input').fill('柔性'); + await page.waitForTimeout(500); +}); +await shot('19-命令面板'); +await page.keyboard.press('Escape'); + +// 20 对话:多策略对比 +await tryStep('策略对比', async () => { + await composer.fill('对比几种策略'); + await composer.press('Enter'); + await page.locator('text=采用').first().waitFor({ state: 'visible', timeout: 130_000 }); +}, 140_000); +await page.waitForTimeout(1000); +await shot('20-对话策略对比'); + +await browser.close(); +fs.writeFileSync(path.join(SHOTS, 'manifest2.json'), JSON.stringify(results, null, 2)); +console.log('完成,共', results.length, '张'); +cleanup(); +process.exit(0); diff --git a/_shots/capture3.mjs b/_shots/capture3.mjs new file mode 100644 index 0000000..7e02ee6 --- /dev/null +++ b/_shots/capture3.mjs @@ -0,0 +1,121 @@ +// 第三轮补拍:柔性甘特 / 世界对比 / 利用率(复制现场世界,修复页签滚动) +import { spawn } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import { createRequire } from 'node:module'; + +const ROOT = path.resolve(import.meta.dirname, '..'); +const require = createRequire(path.join(ROOT, 'apps', 'web', 'package.json')); +const { chromium } = require('playwright'); + +const SHOTS = path.join(ROOT, '_shots'); +const TMP = path.join(SHOTS, 'tmp3'); +fs.rmSync(TMP, { recursive: true, force: true }); +fs.mkdirSync(TMP, { recursive: true }); + +const PY = path.join(ROOT, '.venv', 'Scripts', 'python.exe'); +const VITE = path.join(ROOT, 'apps', 'web', 'node_modules', 'vite', 'bin', 'vite.js'); +const API = 'http://127.0.0.1:8100'; +const UI = 'http://localhost:5399'; + +for (const f of ['world.json', 'master.db', 'approvals.json', 'knowledge.json', 'config.json']) { + const src = path.join(ROOT, 'server', 'data', f); + if (fs.existsSync(src)) fs.copyFileSync(src, path.join(TMP, f)); +} + +const children = []; +function run(name, cmd, args, opts = {}) { + const log = fs.createWriteStream(path.join(SHOTS, `${name}3.log`)); + const p = spawn(cmd, args, { cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'], ...opts }); + p.stdout.pipe(log); p.stderr.pipe(log); + children.push(p); +} +function cleanup() { + for (const p of children) { + try { spawn('taskkill', ['/PID', String(p.pid), '/T', '/F'], { stdio: 'ignore' }); } catch {} + } +} +process.on('exit', cleanup); + +async function waitHttp(url, timeoutMs) { + const t0 = Date.now(); + while (Date.now() - t0 < timeoutMs) { + try { const r = await fetch(url); if (r.status < 500) return; } catch {} + await new Promise(r => setTimeout(r, 1000)); + } + throw new Error('waitHttp timeout: ' + url); +} + +run('backend', PY, ['-m', 'uvicorn', 'server.main:app', '--port', '8100'], { + env: { + ...process.env, + APS_PORT: '8100', + APS_WORLD_PATH: path.join(TMP, 'world.json'), + APS_DB_PATH: path.join(TMP, 'master.db'), + APS_APPROVAL_PATH: path.join(TMP, 'approvals.json'), + APS_KNOWLEDGE_PATH: path.join(TMP, 'knowledge.json'), + APS_WEB_DEMO_SEED: '1', + }, +}); +run('frontend', process.execPath, [VITE, '--port', '5399', '--strictPort'], { + cwd: path.join(ROOT, 'apps', 'web'), + env: { ...process.env, VITE_API_TARGET: API }, +}); + +console.log('等待服务…'); +await waitHttp(API + '/api/health', 120_000); +await waitHttp(UI, 120_000); + +const browser = await chromium.launch(); +const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1.5 }); + +async function shot(name) { + await page.waitForTimeout(700); + await page.screenshot({ path: path.join(SHOTS, name + '.png') }); + console.log(' ✓', name); +} + +// 登录 +await page.goto(UI); +await page.locator('.guest-login-trigger').waitFor({ state: 'visible', timeout: 60_000 }); +await page.locator('.guest-login-trigger').click(); +const dialog = page.locator('.auth-login-dialog'); +await dialog.locator('input[placeholder="请输入企业名称"]').fill('platform'); +await dialog.locator('input[placeholder="请输入用户名"]').fill('zhangzhen'); +await dialog.locator('input[placeholder="请输入密码"]').fill('sz@zz@1116'); +await dialog.locator('.login-submit').click(); +const composer = page.locator('textarea[placeholder^="输入排产指令"]'); +await composer.waitFor({ state: 'visible', timeout: 60_000 }); +await page.waitForTimeout(2500); + +// 柔性排产(让柔性甘特有数据) +await composer.fill('跑一版柔性排产'); +await composer.press('Enter'); +await page.locator('.flex-block-title', { hasText: '柔性排产' }).first() + .waitFor({ state: 'visible', timeout: 170_000 }); +await page.waitForTimeout(1500); + +// 打开视口 +const resultBtn = page.locator('.right-rail button[data-tip="会话结果"], .right-rail button[data-tip="再点收起"]').first(); +if (!(await page.locator('.vp-toolbar').isVisible().catch(() => false))) { + try { await resultBtn.click(); } catch {} +} +await page.locator('.vp-toolbar').waitFor({ state: 'visible', timeout: 20_000 }).catch(() => {}); + +const tab = async (label, name) => { + try { + const t = page.locator('.seg .tab', { hasText: label }).first(); + await t.scrollIntoViewIfNeeded({ timeout: 8_000 }); + await t.click({ timeout: 8_000 }); + await page.waitForTimeout(1500); + await shot(name); + } catch (e) { console.log(' ✗', label, e.message.slice(0, 60)); } +}; +await tab('柔性甘特', '21-柔性甘特'); +await tab('世界对比', '22-世界对比'); +await tab('利用率', '23-利用率'); + +await browser.close(); +console.log('补拍完成'); +cleanup(); +process.exit(0); diff --git a/_shots/manifest.json b/_shots/manifest.json new file mode 100644 index 0000000..d95bf6a --- /dev/null +++ b/_shots/manifest.json @@ -0,0 +1,78 @@ +[ + [ + "01-登录", + "登录弹窗" + ], + [ + "02-主界面", + "登录后的主界面" + ], + [ + "03-对话柔性排产", + "对话发起柔性排产与结果卡" + ], + [ + "04-甘特图", + "甘特图" + ], + [ + "05-负荷热力", + "负荷热力" + ], + [ + "06-交期承诺", + "交期承诺" + ], + [ + "07-分桶计划", + "分桶计划" + ], + [ + "08-KPI仪表盘", + "KPI" + ], + [ + "09-对比表", + "对比表" + ], + [ + "10-产能池", + "产能池" + ], + [ + "12-柔性工作台", + "柔性工作台" + ], + [ + "13-订单管理", + "订单管理" + ], + [ + "14-主数据", + "主数据" + ], + [ + "15-排产结构", + "排产结构" + ], + [ + "16-多智能体编排", + "多智能体编排" + ], + [ + "17-图纸识别", + "图纸识别" + ], + [ + "18-命令面板", + "Ctrl+K 命令面板" + ], + [ + "19-对话策略对比", + "多策略方案卡对比" + ], + [ + "20-对话知识问答", + "知识库问答" + ] +] \ No newline at end of file diff --git a/build-manual-docx.js b/build-manual-docx.js new file mode 100644 index 0000000..16875c0 --- /dev/null +++ b/build-manual-docx.js @@ -0,0 +1,595 @@ +import fs from "node:fs"; +import path from "node:path"; +import { + AlignmentType, + BorderStyle, + Document, + Footer, + Header, + HeadingLevel, + ImportedXmlComponent, + Packer, + PageBreak, + PageNumber, + Paragraph, + ShadingType, + Table, + TableCell, + TableRow, + TextRun, + WidthType, + convertInchesToTwip, +} from "docx"; + +const outputPath = process.argv[2]; +if (!outputPath) { + throw new Error("Usage: node build-manual-docx.js /absolute/path/output.docx"); +} +fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + +const T = String.raw; + +const palette = { + dark: "1F2A36", + primary: "2F4858", + light: "6B7A86", + border: "8C979F", + fill: "F2F5F7", +}; + +const fontBody = { + ascii: "Times New Roman", + hAnsi: "Times New Roman", + cs: "Times New Roman", + eastAsia: "SimSun", +}; +const fontHead = { + ascii: "Times New Roman", + hAnsi: "Times New Roman", + cs: "Times New Roman", + eastAsia: "SimHei", +}; +const fontCode = { + ascii: "Consolas", + hAnsi: "Consolas", + cs: "Consolas", + eastAsia: "SimSun", +}; + +const run = (text, options = {}) => + new TextRun({ text, font: fontBody, size: 24, ...options }); + +const para = (children, options = {}) => + new Paragraph({ + spacing: { after: 120, line: 360 }, + ...options, + children: Array.isArray(children) ? children : [children], + }); + +// 正文:小四宋体,首行缩进两字符,1.5 倍行距 +const body = (text) => + para(run(text), { + indent: { firstLine: 480 }, + alignment: AlignmentType.JUSTIFIED, + }); + +// 章标题:三号黑体;节标题:四号黑体;小节:小四黑体 +const h1 = (text) => + para(new TextRun({ text, font: fontHead, bold: true, size: 32, color: palette.dark }), { + heading: HeadingLevel.HEADING_1, + spacing: { before: 360, after: 240 }, + }); +const h2 = (text) => + para(new TextRun({ text, font: fontHead, bold: true, size: 28, color: palette.dark }), { + heading: HeadingLevel.HEADING_2, + spacing: { before: 280, after: 160 }, + }); +const h3 = (text) => + para(new TextRun({ text, font: fontHead, bold: true, size: 24, color: palette.dark }), { + heading: HeadingLevel.HEADING_3, + spacing: { before: 200, after: 120 }, + }); + +const bullet = (text) => + para(run(text), { bullet: { level: 0 }, indent: { left: 480 } }); + +const note = (text) => + para(run(text, { italics: true, color: palette.light, size: 21 }), { + indent: { firstLine: 480 }, + }); + +const codeBlock = (lines) => + lines.map( + (line) => + new Paragraph({ + spacing: { after: 40, line: 280 }, + indent: { left: 480 }, + shading: { type: ShadingType.CLEAR, fill: palette.fill }, + children: [ + new TextRun({ text: line === "" ? " " : line, font: fontCode, size: 20 }), + ], + }) + ); + +// 学术三线表:顶线、栏目线、底线,无竖线与内部横线 +const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" }; +const table3 = (headers, rows, widths) => { + const total = widths.reduce((a, b) => a + b, 0); + const cellPara = (text, bold) => + new Paragraph({ + spacing: { after: 40, line: 300 }, + children: [ + new TextRun({ text, font: bold ? fontHead : fontBody, size: 21, bold: !!bold }), + ], + }); + const mkCell = (text, i, opts = {}) => + new TableCell({ + children: [cellPara(text, opts.bold)], + width: { size: widths[i], type: WidthType.DXA }, + margins: { top: 80, bottom: 80, left: 100, right: 100 }, + borders: opts.borders, + }); + const headerCells = headers.map((text, i) => + mkCell(text, i, { + bold: true, + borders: { + top: { style: BorderStyle.SINGLE, size: 12, color: palette.dark }, + bottom: { style: BorderStyle.SINGLE, size: 6, color: palette.dark }, + left: noBorder, + right: noBorder, + }, + }) + ); + const bodyRows = rows.map((row, ri) => { + const isLast = ri === rows.length - 1; + return new TableRow({ + children: row.map((text, i) => + mkCell(String(text), i, { + borders: { + top: noBorder, + bottom: isLast + ? { style: BorderStyle.SINGLE, size: 12, color: palette.dark } + : noBorder, + left: noBorder, + right: noBorder, + }, + }) + ), + }); + }); + return new Table({ + width: { size: total, type: WidthType.DXA }, + columnWidths: widths, + rows: [new TableRow({ children: headerCells, tableHeader: true }), ...bodyRows], + }); +}; + +const caption = (text) => + para(new TextRun({ text, font: fontHead, size: 21, bold: true, color: palette.primary }), { + alignment: AlignmentType.CENTER, + spacing: { before: 120, after: 80 }, + }); + +const xmlEscape = (value) => + String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """); + +const toc = (entries) => { + const cached = entries + .map(({ title: entryTitle, level, page }) => { + const indent = Math.max(0, level - 1) * 360; + return ` + + + + + + ${xmlEscape(entryTitle)} + + ${xmlEscape(page)} + `; + }) + .join(""); + return ImportedXmlComponent.fromXmlString(` + + + + + + TOC \\o "1-3" \\h \\z \\u + + + + ${cached} + + + `).root[0]; +}; + +// --------------------------------------------------------------------------- +// 内容流:{t:'h1'|'h2'|'h3'|'p'|'b'|'note'|'code'|'tbl'|'cap'|'pagebreak'} +// --------------------------------------------------------------------------- +const flow = []; +const tocEntries = []; +let estPage = 4; +const H1 = (x) => { + tocEntries.push({ title: x, level: 1, page: estPage++ }); + flow.push({ t: "h1", x }); +}; +const H2 = (x) => { + flow.push({ t: "h2", x }); +}; +const H3 = (x) => flow.push({ t: "h3", x }); +const P = (x) => flow.push({ t: "p", x }); +const B = (x) => flow.push({ t: "b", x }); +const N = (x) => flow.push({ t: "note", x }); +const CODE = (...lines) => flow.push({ t: "code", x: lines }); +const TBL = (cap, headers, rows, widths) => + flow.push({ t: "tbl", cap, h: headers, r: rows, w: widths }); +const PAGEBREAK = () => flow.push({ t: "pagebreak" }); + +// ===================== 第一章 ===================== +H1(T`第一章 系统概述`); +H2(T`1.1 系统定位`); +P(T`工业智核 APS 计划排产智能体是一套以“自然语言对话 + 可视化排产工作台”双交互范式为核心的高级计划与排产系统。系统左侧为对话通道,右侧为可视化工作台,用户以一句话发起业务动作,右侧视图随即刷新,形成“左说右动”的闭环交互。`); +P(T`系统核心业务链路为:销售订单 → MRP 分解(自制 / 采购 / 委外)→ 多策略试排(规则引擎 / CP-SAT / 遗传算法 / NSGA-II)→ 方案对比 → P2 确认发布 → MES 下发 → 报工回流。`); +H2(T`1.2 双排产模型`); +P(T`系统并行维护两条排产轨道,分别对应不同的生产组织形态,界面对当前所走链路予以明确标识,禁止混谈。`); +TBL(T`表 1-1 双排产模型对比`, + [T`轨道`, T`资源模型`, T`适用场景`], + [ + [T`固定轨`, T`产线选线 + 工位占槽(RuleEngine / CP / GA / HYBRID)`, T`产线固定的常规离散制造`], + [T`柔性轨`, T`能力池 + 虚拟产线(PoolEngine)`, T`多品种小批量、设备可移动(如线束 / PDU 共线)`], + ], + [1600, 4200, 3400]); +H2(T`1.3 安全与治理原则`); +P(T`系统一切写操作均经门禁确认卡执行:P2 级动作出具单次确认卡,P3 级外部副作用动作执行二次确认;执行前自动建立检查点,全程写入审计哈希链。试排仅产出 DRAFT 草稿版本,系统不自动发布;回滚执行前自动再次存档,构成可恢复的安全网。`); + +// ===================== 第二章 ===================== +H1(T`第二章 运行环境与系统启动`); +H2(T`2.1 交付形态`); +TBL(T`表 2-1 Web 与桌面双形态对比`, + [T`形态`, T`数据位置`, T`启动方式`, T`账号体系`], + [ + [T`Web`, T`仓库 server/data/(或 APS_HOME)`, T`npm run dev:web,浏览器访问 http://localhost:5173`, T`JMS 企业账号登录`], + [T`桌面`, T`C:\\Users\\<用户>\\.aps(与开发环境隔离)`, T`npm run dev:desktop 或安装 release 目录下的 NSIS 安装包`, T`免账号,授权码激活`], + ], + [1100, 2900, 3100, 2100]); +H2(T`2.2 依赖安装`); +P(T`首次部署需完成 Python 与 Node 双侧依赖安装,在仓库根目录依次执行:`); +CODE( + T`cd D:\\ItemSpace\\14.工业智核\\aps-agent`, + T`python -m venv .venv`, + T`.venv\\Scripts\\activate`, + T`pip install -r requirements.txt`, + T`copy .env.example .env`, + T`npm run bootstrap` +); +H2(T`2.3 Web 端登录配置`); +P(T`Web 端联调用户登录前,须在 .env 文件中配置 JMS 地址与本地会话密钥:`); +CODE( + T`APS_AUTH_PROVIDER=jms`, + T`JMS_AUTH_BASE_URL=https://jms-mes-api.jzplat.com`, + T`JMS_AUTH_SESSION_SECRET=至少32个随机字符`, + T`APS_LICENSE_PROVIDER=mock` +); +P(T`如需限制为单一企业,可追加配置 JMS_AUTH_TENANT_CODE 与 JMS_AUTH_TENANT_NAME。JMS 当前无自助注册与自助找回密码接口,账号创建与密码重置由所属企业管理员处理。`); +H2(T`2.4 启动与打包`); +B(T`开发运行(Web):npm run dev:web,前端 http://localhost:5173,API http://localhost:8000;`); +B(T`开发运行(桌面):npm run dev:desktop,启动 Electron 窗口;`); +B(T`仅起后端:npm run dev:server,OpenAPI 文档位于 http://localhost:8000/docs;`); +B(T`打包 Web:npm run build:web,产物为 apps/web/dist 静态站点;`); +B(T`打包桌面:npm run build:desktop,产物为 apps/desktop/release 下的安装包。`); + +// ===================== 第三章 ===================== +H1(T`第三章 登录授权与界面导览`); +H2(T`3.1 登录与授权`); +P(T`Web 端:打开页面后选择所属企业(企业列表自 JMS 读取),输入真实账号密码登录。桌面端:输入授权码激活,开发演示授权码为 APS-HOUR-DEMO、APS-DAY-DEMO、APS-WEEK-DEMO、APS-MONTH-DEMO、APS-YEAR-DEMO、APS-PERP-DEMO。`); +H2(T`3.2 界面布局`); +P(T`主界面由四区构成:顶部为中文菜单栏(按功能开关过滤);左侧栏承载项目 / 任务与订单、主数据等快捷入口;中部为对话区与工作台;右侧为工具导轨,依次提供多智能体编排、DXF 图纸、柔性工作台、知识库、治理台等工具入口。`); +P(T`对话流中内嵌方案卡、确认卡、知识引用、报告等富交互块,可直接点按操作。右侧视口包括:固定 / 柔性甘特、负荷、交期、分桶计划、利用率、对比表、KPI 仪表盘、产能池与时间线。`); +H2(T`3.3 快捷交互`); +P(T`Ctrl+K 唤起命令面板,可快速跳转任意面板与视图,条目随功能开关配置过滤;当前展开的工具被禁用时面板自动隐藏。`); + +// ===================== 第四章 ===================== +H1(T`第四章 数据准备`); +H2(T`4.1 工程目录导入`); +P(T`于左侧栏选择“新建任务 / 新建项目”,将工程目录指向数据文件夹(内含订单、物料、BOM、工艺、设备、班历等 Excel/CSV 文件),随后在对话框输入“分析一下数据文件”。系统执行项目深度分析,输出订单、物料、工艺、设备、BOM 计数及结构化诊断,并自动导入排产世界。诊断按“有效 / 阻断(blocking)/ 跳过”三档统计,阻断项定位至具体文件与行号。`); +H2(T`4.2 Excel/CSV 导入管线`); +P(T`主数据面板与订单面板均设“导入”入口。导入遵循“表头映射 → 预览校验 → P2 确认落库”三段式管线,支持订单、物料、BOM、工艺路线、设备、模具、工序、区域八类对象。`); +H2(T`4.3 DXF 工程图纸识别`); +P(T`右侧导轨“图纸”入口支持上传 DXF、填写本地路径或扫描工程目录。系统自动解析标题栏(图号、名称、比例、版本,逐字段标注置信度与来源),生成支持缩放、平移与图层开关的 SVG 预览;物料候选经人工核对后生成 P2 确认卡,批准后方写入主数据。`); +N(T`图纸未承载的字段(材质、工时、外协属性、BOM 子件数量等)一律显式标记 missing,绝不编造,必须经人工确认。`); +H2(T`4.4 演示数据装载`); +P(T`柔性排产演示可装载康尼真实单数据,在仓库根目录执行下列脚本,或以口令“导入完整生产路线”替换演示数据:`); +CODE(T`.venv\\Scripts\\python.exe scripts\\load_kangni.py`); +P(T`另提供纯内存、确定性结果的一键全流程模拟脚本(无需启动服务),适用于演示与验收:`); +CODE(T`.venv\\Scripts\\python.exe scripts\\flex_simulation.py`); +H2(T`4.5 主数据录入顺序`); +P(T`主数据之间存在严格依赖关系,录入必须遵循如下顺序:先工厂 / 产线,再物料,再 BOM,再工序,再工艺路线,再产线-产品绑定,最后录入订单。任一环节缺失,后续排产必然失败或结果“找不到依据”。`); + +// ===================== 第五章 ===================== +H1(T`第五章 主数据维护`); +P(T`侧栏“主数据”面板按页签组织,全部编辑动作生成 P2 确认卡,经批准后执行;执行前自动建立检查点并写入审计链。主数据修改仅影响其后生成的新排产版本,历史版本不回写。`); +TBL(T`表 5-1 主数据页签与能力`, + [T`页签`, T`维护内容`, T`权力等级`], + [ + [T`资源`, T`工厂→车间→产线→工位→设备资源树;产线编辑 / 停启用(停用产线不进入新排产)`, T`编辑 P2`], + [T`物料与 BOM`, T`库存 / 在途 / 安全库存 / 前置期;BOM 明细用量与关键料`, T`P2`], + [T`日历与维保`, T`班次 / 日历只读;维保窗口新增 / 取消,联动设备避让`, T`P2`], + [T`工艺`, T`工艺步骤工时、外协标记`, T`P2`], + [T`换型`, T`产品族×产品族换型时间矩阵(口令“查看换型矩阵”)`, T`P2`], + [T`柔性资源`, T`设备虚拟档案(能力集合、可移动性、移动耗时、当前位置、工装适配)与能力池摘要`, T`只读 + Excel 导入`], + ], + [1500, 5700, 2000]); + +// ===================== 第六章 ===================== +H1(T`第六章 订单管理`); +H2(T`6.1 销售订单`); +P(T`左侧栏“订单”面板提供订单列表与明细维护,新增、编辑、取消、完成均走 P2 确认卡。状态为 CANCELLED 或 COMPLETED 的订单不进入新的排产版本;仅 APPROVED 状态订单参与排产。`); +H2(T`6.2 订单池与审核`); +P(T`订单池以泳道看板组织,支持提交、批准、驳回与批量批准。对应口令:“订单池”“提交审核”“批量批准”。`); +H2(T`6.3 预测订单`); +P(T`预测订单台账默认不进入正式排产;可通过“预测纳入试排”执行 what-if 分析,确认后以“预测转正”转为正式 APPROVED 销售订单。相关口令:“查看预测”“新建预测”“预测纳入试排”“预测转正”。`); +H2(T`6.4 紧急插单`); +P(T`订单面板设插单向导,亦可使用口令“插单快评”查看影响评估,再以“采用插单”应用。固定轨执行 rush.evaluate / rush.apply 流程,柔性轨走 flex.rush;当局部修复扰动超阈值时自动升级为全量重排并出具 P2 确认卡。`); +H2(T`6.5 订单追溯`); +P(T`任一订单支持一键追溯完整单据链:订单本体(客户、交期、产品、数量、状态)→ 主数据依据(BOM、工艺路线、库存)→ MRP 分解结果 → 排产版本与工单 → 缺失项(无 BOM / 无路线 / 未绑定产线 / 库存不足)。柔性轨道另沿 FO→BOM→虚拟产线→工单→设备负荷链路追溯。`); + +// ===================== 第七章 ===================== +H1(T`第七章 物料需求分解(MRP)`); +P(T`入口为订单面板“分解建议”“分解此单”,或对话口令“分解订单 SO-001”“执行MRP分解”“生成采购建议”。分解逻辑全部源自主数据,修改主数据即改变分解结果。`); +TBL(T`表 7-1 MRP 分解三分支`, + [T`分支`, T`依据`, T`产出`], + [ + [T`自制`, T`订单明细成品 + 工艺路线`, T`自制建议,交由排产引擎生成生产订单与工单`], + [T`采购`, T`BOM 净需求 = 毛需求 − 库存 − 在途 − 已下达供应`, T`采购建议 DRAFT:数量与建议下单日(按前置期倒排)`], + [T`委外`, T`工艺步骤外协标记`, T`委外建议 DRAFT:产品、工序、数量、需求日`], + ], + [1200, 3800, 4200]); +B(T`分解为 P1 级动作:仅写建议草稿,幂等替换,可重复执行;`); +B(T`下达经口令“下达MRP建议单”触发 P2 确认卡,批准后 DRAFT 转 RELEASED,并自动补全预计到料日,供齐套检查引用;`); +B(T`多层 BOM 完整展开:半成品缺少工艺路线时不伪装为采购建议,缺工艺条目予以明示。`); + +// ===================== 第八章 ===================== +H1(T`第八章 排产操作`); +H2(T`8.1 固定轨试排`); +P(T`口令示例:“试排一版交期优先”“按产能均衡排一下”“换型最小化试排”“战役合并试排”。试排产出 DRAFT 版本与冲突清单,右侧甘特 / 负荷 / 交期视图即时刷新。`); +B(T`策略模板:DELIVERY_FIRST(交期优先)、FIFO、CAPACITY_BALANCE(产能均衡)、CHANGEOVER_MIN(换型最小化)、COST_FIRST、COMPREHENSIVE、CAMPAIGN(批次合并);`); +B(T`求解引擎:RULE、CP-SAT(工序级 + Cumulative 聚合容量)、GA、NSGA-II(三目标 Pareto)、HYBRID;`); +B(T`滚动时域:realtime60m(1 分钟粒度)、short2h、mid2d、long7d 多级窗口。`); +H2(T`8.2 柔性轨排产`); +P(T`口令“跑一版柔性排产”或经右侧“柔性工作台”操作。系统按订单工艺路线逐工序自能力池动态取用设备,临时拼装虚拟产线(计入模具换型与设备移动耗时),用完即散、设备归还池中。柔性甘特支持“虚拟线预演”,并提供正排 / 倒排 / 瓶颈锚三模式对比。`); +TBL(T`表 8-1 柔性排产接口参数(POST /api/flex/schedule)`, + [T`参数`, T`取值`, T`含义`], + [ + [T`sortMode`, T`ASC / DESC / BOTTLENECK`, T`正排 / 倒排 / 瓶颈锚(默认瓶颈锚)`], + [T`orderIds`, T`订单 id 数组`, T`仅排指定订单,缺省排全部`], + ], + [1800, 3200, 4200]); +H2(T`8.3 方案对比与采用`); +P(T`口令“对比几种策略”触发沙盒内多策略并行试排,返回方案卡组(KPI、差异、Pareto 前沿与偏好推荐)。点击“采用此方案”将所选策略落入主干草稿。须注意:对比不等于发布,采用前主干世界保持不变。`); +H2(T`8.4 发布、存档与回滚`); +TBL(T`表 8-2 版本生命周期操作`, + [T`操作`, T`口令`, T`权级`, T`语义`], + [ + [T`发布`, T`发布这个版本`, T`P2`, T`DRAFT→PUBLISHED;仅完整 DRAFT 可发布,执行前自动建档`], + [T`存档`, T`存个档`, T`P1`, T`对话上下文与世界状态成对快照`], + [T`回滚`, T`回滚 / 点时间线检查点`, T`P2`, T`回滚前自动再次存档(安全网)`], + [T`重置`, T`清空数据重新初始化`, T`P2`, T`重播演示种子(电子装配演示工厂)`], + ], + [1200, 2700, 1000, 4300]); +H2(T`8.5 工单拖拽调程`); +P(T`固定与柔性甘特均支持直接拖动工单:15 分钟吸附、拖动过程实时校验、松手生成 P2 确认卡,批准后形成新版本。`); +H2(T`8.6 冲突处理`); +P(T`冲突中心按冲突类型给出修复建议:安全修复为 P1 一键应用;涉及重排的出具 P2 确认卡。硬阻断(缺料、无可用设备等)展示聚合计数、具体对象、证据与修复入口,绝不显示绿色“排产完成”。`); + +// ===================== 第九章 ===================== +H1(T`第九章 计划层功能`); +P(T`计划层(Advanced Planning)在排产前提供中长期粗能力评估与决策支持,各项能力及对应口令如下表。`); +TBL(T`表 9-1 计划层能力一览`, + [T`能力`, T`口令`, T`功能说明`], + [ + [T`时间分桶计划`, T`时间分桶 / 混合分桶`, T`近 7 日 / 中 4 周 / 远月混合时段建模`], + [T`粗能力评估`, T`有限产能粗评 / 无限产能粗评 / 粗能力对比`, T`FINITE / INFINITE 双模式对照`], + [T`可行性分析`, T`可行性分析 / 能否按期`, T`累计粗能力对交期需求,定位缺口桶与订单 slack`], + [T`库存投影`, T`库存投影`, T`期初 + 在途 + 毛需求 + 到货的日 / 周库存曲线,断料与安全库存告警`], + [T`产能平衡`, T`削峰 / 产能平衡`, T`超载桶向空档挪动建议,前后负荷对照`], + [T`产供决策`, T`产供决策 / 加班建议`, T`残留缺口沿加班→扩线→外协给出结构化建议`], + [T`交期承诺`, T`对话询价 / 柔性工作台询价`, T`询单插入模拟,输出乐观 / 预计 / 悲观三区间与资源缺口`], + [T`瓶颈产能`, T`视口“产能池”`, T`日产能 = 瓶颈工序可用工时 ÷ 单件工时;设备停机 / 调区即时重算,含 4h / 1d / 1w 预测与 85 / 95 预警`], + ], + [1900, 2700, 4600]); + +// ===================== 第十章 ===================== +H1(T`第十章 动态调度与异常重排`); +P(T`系统对生产异常实施 L1–L4 分级重排机制,扰动逐级扩大,确认门禁随级别提升。`); +TBL(T`表 10-1 分级重排机制`, + [T`级别`, T`典型触发`, T`重排行为`], + [ + [T`L1`, T`设备故障(优先)`, T`池内备机换机,最小扰动`], + [T`L2`, T`短窗变化`, T`窗内重排,P2 确认`], + [T`L3`, T`日级变化`, T`日窗重排,P2 确认`], + [T`L4`, T`重大异常`, T`全局重排,P2 确认`], + ], + [1100, 2900, 5200]); +P(T`典型场景:可移动设备故障一台时,能力池存在冗余,交期几乎不受影响;单一瓶颈设备(如唯一激光焊接机)故障时,系统立即报告“无可用设备”,并提示启用备机、外协或重新议定交期。WMS 缺料事件触发“缺料 → 重排 → 确认 → MES 下发 → 回执”全链路闭环。`); + +// ===================== 第十一章 ===================== +H1(T`第十一章 结果视图与报表`); +TBL(T`表 11-1 结果视口一览`, + [T`视口`, T`内容`], + [ + [T`甘特`, T`固定 / 柔性双轨,工单拖拽与冲突高亮`], + [T`负荷 / 利用率`, T`产线与设备负荷、柔性设备 7 日热力、班组并发触顶提示`], + [T`交期看板`, T`订单准时 / 延误一览`], + [T`对比表`, T`固定策略与柔性三模式 KPI 矩阵,最优格高亮并标注相对 delta`], + [T`KPI 仪表盘`, T`利用率 / 准时率 / 在制 / 延误汇总(口令“打开仪表盘”)`], + [T`产能池`, T`池负载率、瓶颈色标、短中长期预测、设备调配 What-if`], + [T`时间线`, T`检查点列表,点击即回滚`], + ], + [2100, 7100]); +P(T`报表经口令“生成日报”“版本对比”生成,数字取自冻结快照模板,不经大模型改写。审计支持哈希链校验、Merkle 树、断链检测与归档加密导出。`); + +// ===================== 第十二章 ===================== +H1(T`第十二章 MES 下发与执行闭环`); +P(T`已发布版本经柔性工作台“MES 执行”或下发口令进入下发流程:P3 二次确认后,Mock MES 幂等下发。报工回流经 mes.report(P1)回报进度与完工,订单整单完工状态联动推进。订单页阶段状态取自持久化的闭环问题与 MES 事实,刷新后不退回静态“待确认”。`); + +// ===================== 第十三章 ===================== +H1(T`第十三章 多智能体编排`); +P(T`右侧栏“多智能体编排”面板支持按需组建智能体团队并自动分发执行,操作步骤如下。`); +B(T`创建智能体:输入名称并选择角色(数据解析 / MRP 分解 / 柔性排产 / 报告等);`); +B(T`挂载 Goal:填写标题并选用模板(如“解析→分解→下达→排产→报告”五任务模板),看门狗随即开始监控;`); +B(T`分发执行:看门狗按能力边界将任务分发给空闲智能体,无合适智能体时自动新建;`); +B(T`消息总线:实时显示任务接力过程,亦可手动向任意智能体发消息,对方回报自身状态与能力边界;`); +B(T`看门狗机制:RUNNING 超期(默认 45 秒,可一键调至 10 秒用于演示)触发收回重排,重试耗尽升级 ATTENTION;全部任务完成自动结案 ACHIEVED;任务失败诚实落 FAILED 并升级,不伪报成功。`); +P(T`重复演示前,先在看门狗区执行一次“恢复”(或调用 POST /api/mesh/reset)。`); + +// ===================== 第十四章 ===================== +H1(T`第十四章 对话口令速查`); +TBL(T`表 14-1 自然语言口令速查表`, + [T`类别`, T`口令`], + [ + [T`数据`, T`分析一下数据文件 / 导入完整生产路线 / 清空数据重新初始化`], + [T`MRP`, T`执行MRP分解 / 分解订单 SO-001 / 生成采购建议 / 下达MRP建议单`], + [T`排产`, T`试排一版交期优先 / 按产能均衡排一下 / 换型最小化试排 / 战役合并试排 / 跑一版柔性排产 / 三模式对比`], + [T`方案`, T`对比几种策略 / 采用此方案 / 发布这个版本`], + [T`插单`, T`插单快评 / 采用插单`], + [T`计划层`, T`时间分桶 / 可行性分析 / 能否按期 / 库存投影 / 削峰 / 产供决策 / 加班建议`], + [T`订单`, T`订单池 / 提交审核 / 批量批准 / 查看预测 / 新建预测 / 预测纳入试排 / 预测转正`], + [T`主数据`, T`查看换型矩阵 / 提高VIP权重到10`], + [T`知识`, T`换线有什么规定 / @知识:标题`], + [T`报告`, T`生成日报 / 版本对比 / 打开仪表盘`], + [T`存档`, T`存个档 / 回滚`], + ], + [1500, 7700]); +N(T`意图管线为“正则快路 → LLM JSON”两级结构;LLM 超时或非法输出自动降级为正则意图,知识未命中时系统明确告知,不编造 SOP。`); + +// ===================== 第十五章 ===================== +H1(T`第十五章 治理与安全`); +H2(T`15.1 门禁分级`); +P(T`门禁按 P0–P3 四级划分:P0 只读;P1 写草稿直通;P2 单次确认卡;P3 外部副作用二次确认(当前用于 MES 下发)。审批角色可按动作分级配置。所有写操作的唯一执行通道为 /api/actions/confirm,不存在绕过确认卡的直接执行接口。`); +H2(T`15.2 审计与约束`); +P(T`每次执行均写入审计链,哈希链可校验,支持断链检测(治理台显示断链徽标)、Merkle 树、归档加密与导出。约束配置中心提供 13 类约束目录,可配置启停与硬 / 软属性,硬约束拦截发布。行业规则包支持 SOP 向排产约束的编译,预览后 P2 应用。`); +H2(T`15.3 功能开关`); +P(T`配置文件 server/data/features.json 控制 12 个功能键(visual / orders / master / flex / timeline / drawing / mesh / knowledge / skills / gov / regen / settings)的界面可见性。修改文件后刷新浏览器即生效,无需重启;文件缺失或损坏时全部默认开启(fail-open)。功能开关仅隐藏入口,不替代 Harness 门禁。`); + +// ===================== 第十六章 ===================== +H1(T`第十六章 常见问题与验收`); +H2(T`16.1 常见问题`); +TBL(T`表 16-1 常见问题与处置`, + [T`问题`, T`处置方法`], + [ + [T`排产失败提示缺失项`, T`按第 4.5 节顺序检查主数据,并以订单“追溯”定位具体缺失`], + [T`排产被硬阻断(缺料 / 无设备)`, T`查看冲突中心证据:缺料先下达采购建议;无设备查停用、维保与故障状态`], + [T`试排后想撤回`, T`时间线点击上一检查点回滚(P2),回滚前系统自动再存档`], + [T`三模式对比结果一致`, T`产能宽裕时三模式趋同,压缩交期制造压力后分化`], + [T`删除前端“项目”后数据仍在`, T`项目属前端工作区,排产世界在后端,清世界用“清空数据重新初始化”`], + [T`Web 无法登录`, T`检查 .env 的 JMS 配置与会话密钥;账号问题联系企业管理员`], + [T`查看接口定义`, T`启动后端后访问 http://localhost:8000/docs`], + ], + [3100, 6100]); +H2(T`16.2 验收快速路径`); +B(T`口令“分析一下数据文件”,工程目录导入成功;`); +B(T`口令“执行MRP分解”,采购建议出现;口令“下达MRP建议单”,批准后转 RELEASED;`); +B(T`口令“对比几种策略”,方案卡出现后采用,右侧甘特刷新;`); +B(T`口令“存个档”,点击检查点回滚并完成 P2 确认;`); +B(T`口令“跑一版柔性排产”,虚拟产线与工单出现;`); +B(T`口令“发布这个版本”,P2 批准后版本转 PUBLISHED;`); +B(T`回归测试:pytest tests/golden -q(当前基线 1071 项通过,1 项既有失败 test_preference_features,与业务无关)。`); + +// ===================== 文档装配 ===================== +const renderItem = (item) => { + switch (item.t) { + case "h1": return [h1(item.x)]; + case "h2": return [h2(item.x)]; + case "h3": return [h3(item.x)]; + case "p": return [body(item.x)]; + case "b": return [bullet(item.x)]; + case "note": return [note(item.x)]; + case "code": return codeBlock(item.x); + case "tbl": return [caption(item.cap), table3(item.h, item.r, item.w), para(run(""))]; + case "pagebreak": return [new Paragraph({ children: [new PageBreak()] })]; + default: return []; + } +}; + +const coverChildren = [ + para(run(T`工业智核 APS 计划排产智能体`, { bold: true, size: 48, color: palette.dark, font: fontHead }), { + alignment: AlignmentType.CENTER, + spacing: { before: 3600, after: 240 }, + }), + para(run(T`操 作 手 册`, { bold: true, size: 44, color: palette.dark, font: fontHead }), { + alignment: AlignmentType.CENTER, + spacing: { after: 1200 }, + }), + para(run(T`—— 基于系统当前能力的标准作业规程 ——`, { size: 24, color: palette.light }), { + alignment: AlignmentType.CENTER, + spacing: { after: 2400 }, + }), + para(run(T`文档版本:V1.0`, { size: 24 }), { alignment: AlignmentType.CENTER, spacing: { after: 160 } }), + para(run(T`编制日期:2026 年 9 月 1 日`, { size: 24 }), { alignment: AlignmentType.CENTER, spacing: { after: 160 } }), + para(run(T`适用对象:计划员、实施顾问、演示与验收人员`, { size: 24 }), { alignment: AlignmentType.CENTER }), + new Paragraph({ children: [new PageBreak()] }), +]; + +const prefaceChildren = [ + h1(T`前 言`), + body(T`本手册依据工业智核 APS 计划排产智能体当前代码能力编制,内容对齐产品功能清单(docs/product/features.md)、核心业务旅程(docs/product/journeys.md)及截至 2026 年 8 月 26 日的变更记录(含 round-65 至 round-67、DXF-01 图纸识别、FF-01 功能开关)。`), + body(T`手册共十六章:第一至三章介绍系统定位、运行环境与界面;第四至八章按业务顺序阐述数据准备、主数据、订单、MRP 与排产操作;第九至十三章阐述计划层、动态调度、结果视图、MES 闭环与多智能体编排;第十四至十六章给出口令速查、治理安全与常见问题处置。`), + body(T`本手册仅描述当前代码真实路径。凡标注权级(P0–P3)的操作,其执行均受门禁确认卡约束,任何绕过确认的执行通道在系统中不存在。`), + new Paragraph({ children: [new PageBreak()] }), + para(run(T`目 录`, { bold: true, size: 32, color: palette.dark, font: fontHead }), { + alignment: AlignmentType.CENTER, + spacing: { before: 240, after: 240 }, + }), + note(T`打开文档后,右键目录选择“更新域”即可刷新页码。`), + toc(tocEntries), + new Paragraph({ children: [new PageBreak()] }), +]; + +const bodyChildren = []; +for (const item of flow) { + bodyChildren.push(...renderItem(item)); +} + +const doc = new Document({ + features: { updateFields: true }, + sections: [ + { + properties: { + page: { + size: { width: 11906, height: 16838 }, + margin: { top: 1440, bottom: 1440, left: 1560, right: 1560 }, + }, + }, + headers: { + default: new Header({ + children: [ + para(run(T`工业智核 APS 计划排产智能体 · 操作手册`, { size: 18, color: palette.light }), { + alignment: AlignmentType.CENTER, + spacing: { after: 0 }, + }), + ], + }), + }, + footers: { + default: new Footer({ + children: [ + para(new TextRun({ children: [PageNumber.CURRENT], size: 18 }), { + alignment: AlignmentType.CENTER, + spacing: { after: 0 }, + }), + ], + }), + }, + children: [...coverChildren, ...prefaceChildren, ...bodyChildren], + }, + ], +}); + +const buffer = await Packer.toBuffer(doc); +fs.writeFileSync(outputPath, buffer); diff --git a/build-manual-v2.js b/build-manual-v2.js new file mode 100644 index 0000000..8f42645 --- /dev/null +++ b/build-manual-v2.js @@ -0,0 +1,321 @@ +// ============================================================ +// 图文版操作手册(大白话 + 真实截图)生成脚本 +// ============================================================ +import fs from "node:fs"; +import path from "node:path"; +import { + AlignmentType, BorderStyle, Document, Footer, Header, HeadingLevel, + ImageRun, Packer, PageBreak, PageNumber, Paragraph, ShadingType, + Table, TableCell, TableRow, TextRun, WidthType, +} from "docx"; + +const outputPath = process.argv[2]; +if (!outputPath) throw new Error("Usage: node build-manual-v2.js /abs/out.docx"); +const ROOT = path.dirname(outputPath); +const SHOTS = path.join(ROOT, "_shots"); + +const T = String.raw; +const palette = { dark: "1F2A36", primary: "2F4858", light: "6B7A86", fill: "F2F5F7" }; +const fontBody = { ascii: "Times New Roman", hAnsi: "Times New Roman", cs: "Times New Roman", eastAsia: "SimSun" }; +const fontHead = { ascii: "Times New Roman", hAnsi: "Times New Roman", cs: "Times New Roman", eastAsia: "SimHei" }; + +const run = (text, options = {}) => new TextRun({ text, font: fontBody, size: 24, ...options }); +const para = (children, options = {}) => new Paragraph({ + spacing: { after: 140, line: 340 }, ...options, + children: Array.isArray(children) ? children : [children], +}); +const body = (text) => para(run(text), { indent: { firstLine: 480 }, alignment: AlignmentType.JUSTIFIED }); +const h1 = (text) => para(new TextRun({ text, font: fontHead, bold: true, size: 32, color: palette.dark }), { + heading: HeadingLevel.HEADING_1, spacing: { before: 360, after: 200 }, +}); +const h2 = (text) => para(new TextRun({ text, font: fontHead, bold: true, size: 26, color: palette.primary }), { + heading: HeadingLevel.HEADING_2, spacing: { before: 240, after: 140 }, +}); +const tip = (text) => para( + [new TextRun({ text: T`💡 `, size: 24 }), run(text, { color: palette.primary })], + { indent: { left: 240 }, shading: { type: ShadingType.CLEAR, fill: palette.fill } }, +); + +let figNo = 0; +const fig = (file, captionText) => { + figNo += 1; + const img = new ImageRun({ + type: "png", + data: fs.readFileSync(path.join(SHOTS, file)), + transformation: { width: 640, height: 400 }, + }); + return [ + new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 120, after: 60 }, children: [img] }), + para(new TextRun({ text: T`图 ${figNo} ${captionText}`, font: fontHead, size: 20, color: palette.light }), { + alignment: AlignmentType.CENTER, spacing: { after: 200 }, + }), + ]; +}; + +const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" }; +const table3 = (headers, rows, widths) => { + const total = widths.reduce((a, b) => a + b, 0); + const mk = (text, i, opts = {}) => new TableCell({ + children: [new Paragraph({ + spacing: { after: 30, line: 300 }, + children: [new TextRun({ text, font: opts.bold ? fontHead : fontBody, size: 21, bold: !!opts.bold })], + })], + width: { size: widths[i], type: WidthType.DXA }, + margins: { top: 70, bottom: 70, left: 100, right: 100 }, + borders: opts.borders, + }); + return new Table({ + width: { size: total, type: WidthType.DXA }, + columnWidths: widths, + rows: [ + new TableRow({ + tableHeader: true, + children: headers.map((h, i) => mk(h, i, { + bold: true, + borders: { + top: { style: BorderStyle.SINGLE, size: 12, color: palette.dark }, + bottom: { style: BorderStyle.SINGLE, size: 6, color: palette.dark }, + left: noBorder, right: noBorder, + }, + })), + }), + ...rows.map((row, ri) => new TableRow({ + children: row.map((c, i) => mk(String(c), i, { + borders: { + top: noBorder, + bottom: ri === rows.length - 1 + ? { style: BorderStyle.SINGLE, size: 12, color: palette.dark } : noBorder, + left: noBorder, right: noBorder, + }, + })), + })), + ], + }); +}; + +const children = []; +const add = (...items) => children.push(...items); +const PAGEBREAK = () => add(new Paragraph({ children: [new PageBreak()] })); + +// ---------------- 封面 ---------------- +add( + para(new TextRun({ text: T`工业智核 APS 排产智能体`, font: fontHead, bold: true, size: 48, color: palette.dark }), { + alignment: AlignmentType.CENTER, spacing: { before: 3200, after: 240 }, + }), + para(new TextRun({ text: T`图 文 使 用 手 册`, font: fontHead, bold: true, size: 44, color: palette.dark }), { + alignment: AlignmentType.CENTER, spacing: { after: 900 }, + }), + para(run(T`—— 全截图实操版 · 看完就会用 ——`, { color: palette.light }), { + alignment: AlignmentType.CENTER, spacing: { after: 2400 }, + }), + para(run(T`版本:V1.0(按 2026-09-01 系统实际界面截图)`), { alignment: AlignmentType.CENTER, spacing: { after: 160 } }), + para(run(T`适合谁看:计划员、车间调度、实施和售前同事`), { alignment: AlignmentType.CENTER }), + new Paragraph({ children: [new PageBreak()] }), +); + +// ---------------- 写在前面 ---------------- +add( + h1(T`写在前面`), + body(T`这本手册不讲原理、不堆术语,就干一件事:照着截图,一步一步告诉你这个系统每个功能是干嘛的、在哪点、怎么用。手册里每一张图都是从真实系统上现截的,你照着点,看到的就是图上那样。`), + body(T`这个系统一句话就能说清:它是一个会听人话的排产助手。你在左边跟它聊天(比如“跑一版柔性排产”),它在右边把甘特图、产能、订单这些画面给你摆出来。排产行业里管这叫 APS(高级计划排产),你就把它当成一个特别懂生产、还特别守规矩的计划员搭档就行。`), + body(T`说它守规矩,是因为系统里有个铁律:凡是动真格的操作(发布版本、下达采购、改主数据、下发车间),它都会先弹一张“确认卡”让你点“批准”才执行,执行前还自动拍个快照存档。所以放心大胆地点,错了随时能回滚。`), +); + +// ---------------- 一、登录 ---------------- +PAGEBREAK(); +add( + h1(T`一、登录:选对企业,输账号密码`), + body(T`打开系统后,先点左下角的“登录”。在弹窗里填三样东西:企业名称、用户名、密码,然后点登录按钮。账号由你们企业的管理员开通,忘了密码也找管理员,系统本身没有自助注册。`), + ...fig("01-登录.png", T`登录弹窗:企业名称 + 用户名 + 密码`), + tip(T`如果你用的是桌面版(安装包装的那个),不用账号,输入授权码就能进。`), +); + +// ---------------- 二、主界面 ---------------- +add( + h1(T`二、先认门:主界面分三块`), + body(T`登录进来看到的就是主界面。记住三块区域,后面所有操作都绕不开它们:`), + ...fig("02-主界面.png", T`主界面:左边办事入口,中间聊天,右边工具条`), + table3( + [T`区域`, T`叫什么`, T`干嘛用的`], + [ + [T`左边竖栏`, T`导航栏`, T`新建任务、看订单、管主数据、进柔性工作台;下面是你的项目和任务列表`], + [T`中间大块`, T`对话区`, T`跟系统聊天的地方。底下输入框打字,回车发送;系统回的话里会带各种卡片,能直接点`], + [T`右边细条`, T`工具导轨`, T`一列小图标:会话结果、订单、主数据、柔性工作台、多智能体、图纸识别……点哪个,哪个就在右边展开`], + ], + [1700, 1700, 5800], + ), + body(T`刚进系统时中间会有一张欢迎页,给你三个常用建议(跑一版柔性排产、试排一版交期优先、多策略方案对比),点一下就等于把这句话打进对话区,新手从这儿开始最省事。`), +); + +// ---------------- 三、对话区 ---------------- +PAGEBREAK(); +add( + h1(T`三、动嘴就能干活:对话区`), + h2(T`3.1 让它排一版产`), + body(T`在底部输入框打一句“跑一版柔性排产”,回车。系统想一会儿,就会回你一张结果卡:排了多少条虚拟产线、多少张工单、最晚哪天完工、有几张单能准时、有没有冲突。有什么料缺、什么工艺没确认,它也会老老实实列出来,绝不装没事。`), + ...fig("03-对话柔性排产.png", T`说一句“跑一版柔性排产”,结果直接打在对话里`), + tip(T`常用的说法还有:“试排一版交期优先”“按产能均衡排一下”“换型最小化试排”。注意“试排”只是草稿,不会动真数据。`), + h2(T`3.2 让几个策略打擂台`), + body(T`拿不准哪种排法好?说一句“对比几种策略”。系统会在后台沙盒里把交期优先、产能均衡、换型最小化、战役合并、综合优化几种打法各跑一遍,给你一溜方案卡,每张卡上写清延迟、冲突、利用率、换型次数。看中哪张点“采用此方案”,它就变成正式草稿。`), + + ...fig("19-对话策略对比.png", T`“对比几种策略”:五种打法并排比,选好就采用`), + h2(T`3.3 有规矩不懂,直接问`), + body(T`系统里有个知识库,装着你们的工艺规定、SOP 这些文档。直接问“换线有什么规定”,它会把相关规定翻出来给你,并且标明出处;没查到就老实说没查到,绝不瞎编。`), + + ...fig("20-对话知识问答.png", T`知识问答:答案带来源,查不到就老实说没有`), +); + +// ---------------- 四、柔性工作台 ---------------- +PAGEBREAK(); +add( + h1(T`四、不爱打字?点按钮也行:柔性工作台`), + body(T`点右边工具导轨的“柔性工作台”,右边就展开一个操作台,排产能点的按钮都在这儿。这是为“多品种小批量、设备能挪来挪去”的车间准备的:系统不问“走哪条产线”,只问“哪道工序”,从能干这道工序的设备池里临时挑设备,拼一条一次性的“虚拟产线”,用完就散。`), + ...fig("12-柔性工作台.png", T`柔性工作台:排产、询价、异常、冲突、What-if 一页搞定`), + body(T`台面上你能干这些事:`), + table3( + [T`按钮区`, T`点了会怎样`], + [ + [T`瓶颈锚排产 / 正排 / 倒排`, T`三种排法任选:按交期顺推、从交期倒推、或者卡住最慢的瓶颈工序往两头排`], + [T`三模式对比`, T`三种排法一起跑,摆在一起比,哪种好一眼看见`], + [T`滚动时域`, T`选排产看多远:实时 1 小时、短窗 2 小时、中窗 2 天、长窗 7 天`], + [T`分级重排`, T`出异常时选动作幅度:L2 短窗、L3 日窗、L4 全局重排(都要你确认才动)`], + [T`交期询价`, T`销售问“这批啥时候能交”,把产品和数量填进去,给出乐观 / 预计两个交期和缺口提示`], + [T`SAP 集成 / MES 执行`, T`跟 ERP 拉单回写、把定稿的排产下发车间(下发要二次确认)`], + ], + [2600, 6600], + ), + tip(T`截图下方还能看到“当前限制性瓶颈:喷涂,日产能≈1728件”——系统永远告诉你全厂最慢的那道工序是谁,产能就按它算,不虚报。`), +); + +// ---------------- 五、看结果 ---------------- +PAGEBREAK(); +add( + h1(T`五、排完了去哪看:结果视图`), + body(T`排产有结果后,点右边导轨第一个图标“会话结果”,右边就打开一排页签:甘特图、负荷热力、利用率、交期承诺、分桶计划、KPI、对比表、世界对比、产能池。挨个看:`), + h2(T`5.1 产能池:全厂家底一页看清`), + body(T`每个工序能力池一张卡:日产能多少、几台设备、负载百分之几,未来 4 小时 / 1 天 / 1 周的预测都在。最上面黄条永远标着“限制性瓶颈”是谁。哪台设备停了、调走了,这里马上重算。`), + ...fig("09-产能池.png", T`产能池仪表盘:瓶颈一目了然,设备停了立刻重算`), + h2(T`5.2 利用率:哪台设备闲、哪台累`), + body(T`设备热力图按天涂色:绿的是闲(<50%),黄橙渐忙,红的是超载(≥100%)。哪台设备天天红、哪台天天绿,排产合不合理一眼就知道。`), + ...fig("23-利用率.png", T`资源利用率:设备 7 天热力图,红黄绿一眼分明`), + h2(T`5.3 KPI 仪表盘:老板要的数都在这`), + body(T`平均利用率、准时率、总延迟、在制工单、未解冲突、MES 完工率,六张卡汇总固定轨和柔性轨两本账。`), + + ...fig("08-KPI仪表盘.png", T`运营 KPI:利用率、准时率、延迟、冲突一屏汇总`), + h2(T`5.4 分桶计划:看得更远一点`), + body(T`这是给“排产之前”做打算的:近 7 天按日、中期按周、远期按月,把产能和需求放进桶里比。还能点“可行性”看订单能不能按期、点“库存投影”看哪天会断料、点“削峰”让系统建议把超产的桶往空档挪。`), + ...fig("07-分桶计划.png", T`分桶计划:粗能力、可行性、库存投影、削峰都在这`), + h2(T`5.5 对比表与世界对比`), + body(T`“对比表”把几种策略的指标排成矩阵,最优的格子高亮;“世界对比”把两个版本的世界状态并排摆开,订单、资源占用、KPI 差了多少逐项列——回滚前后、方案前后差在哪,不用猜。`), + ...fig("22-世界对比.png", T`两个世界对比:版本之间差了什么,逐项摆开`), + tip(T`甘特图页签在试排出正式版本后,会把每张工单画成时间条,工单还能直接用手拖着调(15 分钟吸附,拖完出确认卡,批准才生效)。`), +); + +// ---------------- 六、订单 ---------------- +PAGEBREAK(); +add( + h1(T`六、订单管理:从接单到下发一条链`), + body(T`点右边导轨“订单管理”。最上面是订单总数、待审、加急、预测四个数;下面一排是“闭环 APS 阶段”流水线,把一张单子的命分成六站:商务订单 → BOM/供需净算 → 制造需求准入 → 排产草案 → P2 发布 → P3 下发 MES。每站都有独立门禁,过不去就卡在那站,系统会告诉你缺什么。`), + ...fig("13-订单管理.png", T`订单管理:六站流水线,卡在哪站、缺什么,写得明明白白`), + body(T`这里你还能:新增订单、批量批准待审订单、一键清理、导入 Excel/CSV 订单、看“排产分析报告”。订单相关的口令也好使:“订单池”“提交审核”“批量批准”“插单快评”。`), +); + +// ---------------- 七、主数据 ---------------- +add( + h1(T`七、主数据:排产的米和锅`), + body(T`点右边导轨“主数据”。排产准不准,七分靠主数据。这里分几个页签:资源(工厂→车间→产线→设备的家谱树)、工艺模型(物料 / BOM / 工序 / 工艺路线 / 产线绑定 / 换型矩阵)、柔性资源、日历与维保、工时维护。`), + + ...fig("14-主数据.png", T`主数据:工艺模型页签,物料、BOM、工序、路线都在这`), + body(T`录数据有个铁顺序,千万别乱:先工厂产线 → 再物料 → 再 BOM → 再工序 → 再工艺路线 → 再绑定产线,最后才录订单。缺一环,排产要么失败,要么排出来你也找不到依据。改任何主数据都会弹确认卡,而且只影响之后的新版本,不会偷偷改历史。`), + tip(T`懒得一条条录?点“导入 Excel/CSV”,先预览校验再确认入库;订单、物料、BOM、路线、设备、模具都能导。`), +); + +// ---------------- 八、排产结构 ---------------- +add( + h1(T`八、排产结构:一张单凭什么这么排`), + body(T`点右边导轨“排产结构”,能看到当前排产版本的结构化分解:哪张订单拆了哪几道工序、占了哪台设备哪个时间段。排产结果不是一张孤零零的图,每道工序都能回指到订单、BOM 和工艺——“凭什么这么排”永远有据可查。`), + ...fig("15-排产结构.png", T`排产结构:订单到工序到设备的完整拆解`), +); + +// ---------------- 九、多智能体 ---------------- +PAGEBREAK(); +add( + h1(T`九、多智能体编排:让一群 AI 替你跑流程`), + body(T`点右边导轨“多智能体编排”。这功能相当于组建一支 AI 小分队:你创建一个 Goal(比如“锐扬排产全流程”,自带解析数据 → MRP 分解 → 下达 → 排产 → 出报告五件事),点“分发执行”,系统就把活派给有对应本事的智能体,没有合适的就现场造一个。`), + ...fig("16-多智能体编排.png", T`多智能体编排:挂 Goal、自动分发、看门狗盯梢`), + body(T`底下有个“看门狗”盯着:哪个任务超时(默认 45 秒)就收回来重排,反复失败就升级叫人来看;全干完自动结案。消息总线里能实时看到智能体之间接力传话。干砸了的任务会老实标 FAILED,不会假装成功。`), +); + +// ---------------- 十、图纸识别 ---------------- +add( + h1(T`十、图纸识别:DXF 图纸直接变主数据`), + body(T`点右边导轨“图纸识别”,把 CAD 的 DXF 图纸传上去(或填个路径、扫个文件夹)。系统自动读标题栏:图号、名称、比例、版本,每个字段都标了可信度;图纸还能在页面上缩放、平移、按图层开关地预览。`), + + ...fig("17-图纸识别.png", T`图纸识别:读标题栏、标置信度、SVG 预览`), + body(T`认出来的物料信息会变成“候选”,你核对一遍、点确认,才正式写进主数据。图纸上没有的信息(材质、工时、BOM 子件数量),系统一律标“缺失”,绝不替你瞎猜。`), +); + +// ---------------- 十一、命令面板 ---------------- +add( + h1(T`十一、懒人神器:Ctrl+K 命令面板`), + body(T`键盘按 Ctrl+K,屏幕中央弹出命令面板。想找什么功能直接打字过滤,比如输“柔性”,“柔性工作台”“柔性甘特”就都冒出来了,回车直达。所有面板和视图都能这么跳。`), + ...fig("18-命令面板.png", T`Ctrl+K:输俩字,功能直达`), +); + +// ---------------- 十二、安全规矩 ---------------- +PAGEBREAK(); +add( + h1(T`十二、几条安全规矩(知道就不慌)`), + table3( + [T`规矩`, T`通俗解释`], + [ + [T`确认卡`, T`凡是动真格的(发布、下达、改主数据、下发 MES),先弹卡让你批准,不批不执行`], + [T`自动存档`, T`每次执行确认卡之前,系统自动拍快照。你也可以随时说“存个档”`], + [T`随时回滚`, T`点时间线上的存档就能回到过去,回滚前还会再帮你存一次现在的状态`], + [T`试排不动真数据`, T`“试排”“对比策略”都在草稿或沙盒里玩,主干数据分毫不动`], + [T`审计留痕`, T`谁、什么时候、批了什么,全部记账且改不了(哈希链),出了事能倒查`], + [T`不装成功`, T`排不动、缺料、没设备,系统直接红色阻断并写清原因,不会给你假绿灯`], + ], + [2200, 7000], + ), +); + +// ---------------- 附录 ---------------- +add( + h1(T`附录:张口就来的口令大全`), + table3( + [T`想干啥`, T`就说`], + [ + [T`排产`, T`试排一版交期优先 / 按产能均衡排一下 / 换型最小化试排 / 跑一版柔性排产 / 三模式对比`], + [T`比方案`, T`对比几种策略 → 看中后点“采用此方案” → “发布这个版本”`], + [T`订单分解`, T`执行MRP分解 / 分解订单 SO-001 / 生成采购建议 / 下达MRP建议单`], + [T`插单`, T`插单快评 → 看影响 → 采用插单`], + [T`看计划层`, T`时间分桶 / 可行性分析 / 能否按期 / 库存投影 / 削峰 / 产供决策`], + [T`管订单`, T`订单池 / 提交审核 / 批量批准 / 查看预测 / 预测转正`], + [T`问规矩`, T`换线有什么规定 / @知识:标题`], + [T`出报表`, T`生成日报 / 版本对比 / 打开仪表盘`], + [T`存档后悔`, T`存个档 / 回滚`], + [T`数据`, T`分析一下数据文件 / 导入完整生产路线 / 清空数据重新初始化`], + ], + [2000, 7200], + ), + body(T`最后一句:系统答不上来的话会老实说“听不懂”或“没查到”,绝不糊弄你。遇到红色阻断别硬来,按它给的修复入口把缺的料、缺的工艺补上,再重新排就是了。`), +); + +const doc = new Document({ + features: { updateFields: false }, + sections: [{ + properties: { + page: { size: { width: 11906, height: 16838 }, margin: { top: 1440, bottom: 1440, left: 1440, right: 1440 } }, + }, + headers: { + default: new Header({ children: [para(run(T`工业智核 APS 排产智能体 · 图文使用手册`, { size: 18, color: palette.light }), { alignment: AlignmentType.CENTER, spacing: { after: 0 } })] }), + }, + footers: { + default: new Footer({ children: [para(new TextRun({ children: [PageNumber.CURRENT], size: 18 }), { alignment: AlignmentType.CENTER, spacing: { after: 0 } })] }), + }, + children, + }], +}); + +fs.writeFileSync(outputPath, await Packer.toBuffer(doc)); diff --git a/build-manual-v3.js b/build-manual-v3.js new file mode 100644 index 0000000..eeaf274 --- /dev/null +++ b/build-manual-v3.js @@ -0,0 +1,316 @@ +// ============================================================ +// 图文版操作手册 v3:专业平实文风 + 真实截图 +// ============================================================ +import fs from "node:fs"; +import path from "node:path"; +import { + AlignmentType, BorderStyle, Document, Footer, Header, HeadingLevel, + ImageRun, Packer, PageBreak, PageNumber, Paragraph, ShadingType, + Table, TableCell, TableRow, TextRun, WidthType, +} from "docx"; + +const outputPath = process.argv[2]; +if (!outputPath) throw new Error("Usage: node build-manual-v3.js /abs/out.docx"); +const ROOT = path.dirname(outputPath); +const SHOTS = path.join(ROOT, "_shots"); + +const T = String.raw; +const palette = { dark: "1F2A36", primary: "2F4858", light: "6B7A86", fill: "F2F5F7" }; +const fontBody = { ascii: "Times New Roman", hAnsi: "Times New Roman", cs: "Times New Roman", eastAsia: "SimSun" }; +const fontHead = { ascii: "Times New Roman", hAnsi: "Times New Roman", cs: "Times New Roman", eastAsia: "SimHei" }; + +const run = (text, options = {}) => new TextRun({ text, font: fontBody, size: 24, ...options }); +const para = (children, options = {}) => new Paragraph({ + spacing: { after: 140, line: 340 }, ...options, + children: Array.isArray(children) ? children : [children], +}); +const body = (text) => para(run(text), { indent: { firstLine: 480 }, alignment: AlignmentType.JUSTIFIED }); +const h1 = (text) => para(new TextRun({ text, font: fontHead, bold: true, size: 32, color: palette.dark }), { + heading: HeadingLevel.HEADING_1, spacing: { before: 360, after: 200 }, +}); +const h2 = (text) => para(new TextRun({ text, font: fontHead, bold: true, size: 26, color: palette.primary }), { + heading: HeadingLevel.HEADING_2, spacing: { before: 240, after: 140 }, +}); +const tip = (text) => para( + [new TextRun({ text: T`提示:`, font: fontHead, bold: true, size: 24, color: palette.primary }), run(text, { color: palette.primary })], + { indent: { left: 240 }, shading: { type: ShadingType.CLEAR, fill: palette.fill } }, +); + +let figNo = 0; +const fig = (file, captionText) => { + figNo += 1; + const img = new ImageRun({ + type: "png", + data: fs.readFileSync(path.join(SHOTS, file)), + transformation: { width: 640, height: 400 }, + }); + return [ + new Paragraph({ alignment: AlignmentType.CENTER, spacing: { before: 120, after: 60 }, children: [img] }), + para(new TextRun({ text: T`图 ${figNo} ${captionText}`, font: fontHead, size: 20, color: palette.light }), { + alignment: AlignmentType.CENTER, spacing: { after: 200 }, + }), + ]; +}; + +const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" }; +const table3 = (headers, rows, widths) => { + const total = widths.reduce((a, b) => a + b, 0); + const mk = (text, i, opts = {}) => new TableCell({ + children: [new Paragraph({ + spacing: { after: 30, line: 300 }, + children: [new TextRun({ text, font: opts.bold ? fontHead : fontBody, size: 21, bold: !!opts.bold })], + })], + width: { size: widths[i], type: WidthType.DXA }, + margins: { top: 70, bottom: 70, left: 100, right: 100 }, + borders: opts.borders, + }); + return new Table({ + width: { size: total, type: WidthType.DXA }, + columnWidths: widths, + rows: [ + new TableRow({ + tableHeader: true, + children: headers.map((h, i) => mk(h, i, { + bold: true, + borders: { + top: { style: BorderStyle.SINGLE, size: 12, color: palette.dark }, + bottom: { style: BorderStyle.SINGLE, size: 6, color: palette.dark }, + left: noBorder, right: noBorder, + }, + })), + }), + ...rows.map((row, ri) => new TableRow({ + children: row.map((c, i) => mk(String(c), i, { + borders: { + top: noBorder, + bottom: ri === rows.length - 1 + ? { style: BorderStyle.SINGLE, size: 12, color: palette.dark } : noBorder, + left: noBorder, right: noBorder, + }, + })), + })), + ], + }); +}; + +const children = []; +const add = (...items) => children.push(...items); +const PAGEBREAK = () => add(new Paragraph({ children: [new PageBreak()] })); + +// ---------------- 封面 ---------------- +add( + para(new TextRun({ text: T`工业智核 APS 计划排产智能体`, font: fontHead, bold: true, size: 48, color: palette.dark }), { + alignment: AlignmentType.CENTER, spacing: { before: 3200, after: 240 }, + }), + para(new TextRun({ text: T`图 文 使 用 手 册`, font: fontHead, bold: true, size: 44, color: palette.dark }), { + alignment: AlignmentType.CENTER, spacing: { after: 900 }, + }), + para(run(T`—— 基于系统真实界面的功能操作指南 ——`, { color: palette.light }), { + alignment: AlignmentType.CENTER, spacing: { after: 2400 }, + }), + para(run(T`版本:V1.1(按 2026-09-01 系统实际运行界面截图)`), { alignment: AlignmentType.CENTER, spacing: { after: 160 } }), + para(run(T`适用对象:计划员、生产调度、实施顾问及售前人员`), { alignment: AlignmentType.CENTER }), + new Paragraph({ children: [new PageBreak()] }), +); + +// ---------------- 手册说明 ---------------- +add( + h1(T`手册说明`), + body(T`本手册以系统真实运行界面的截图为基础,逐功能说明工业智核 APS 计划排产智能体的操作方法。手册遵循“先定位、后操作、再核对”的编写原则:每个功能均说明其业务用途、界面入口与操作要点,读者可按图索骥完成全部日常操作。`), + body(T`系统采用“自然语言对话 + 可视化工作台”双交互模式:左侧对话区接收业务指令(如“跑一版柔性排产”),右侧工作台同步呈现甘特、产能、订单等可视化结果。系统覆盖销售订单接入、MRP 分解、多策略排产、方案对比、版本发布、MES 下发与报工回流的完整业务闭环。`), + body(T`系统的核心治理原则是门禁确认机制:凡涉及正式生效的操作(版本发布、采购下达、主数据修改、MES 下发),均须经由确认卡人工批准后执行,执行前自动创建检查点快照。因此日常操作具备可回滚性,误操作可通过时间线检查点恢复。`), +); + +// ---------------- 一、系统登录 ---------------- +PAGEBREAK(); +add( + h1(T`一、系统登录`), + body(T`打开系统后,点击左下角“登录”按钮,在登录弹窗中依次填写企业名称、用户名与密码,提交后进入工作台。企业账号由所属企业管理员在 JMS 平台统一开通与维护,系统不提供自助注册与自助找回密码功能。`), + ...fig("01-登录.png", T`登录弹窗:企业名称、用户名、密码三项必填`), + tip(T`桌面端(安装包形态)不使用账号体系,输入有效授权码即可激活使用。`), +); + +// ---------------- 二、主界面导览 ---------------- +add( + h1(T`二、主界面导览`), + body(T`登录后的主界面划分为三个功能区域,后续全部操作均围绕这三区展开:`), + ...fig("02-主界面.png", T`主界面布局:左侧导航、中部对话区、右侧工具导轨`), + table3( + [T`区域`, T`名称`, T`功能说明`], + [ + [T`左侧竖栏`, T`导航栏`, T`提供新建任务、订单管理、主数据、柔性工作台等入口,下方为项目与任务列表`], + [T`中部区域`, T`对话区`, T`自然语言指令的输入与回复区域;系统回复中内嵌方案卡、确认卡等可交互组件`], + [T`右侧细条`, T`工具导轨`, T`纵向排列的功能图标,单击即可在右侧展开对应面板(会话结果、订单、主数据、多智能体、图纸识别等)`], + ], + [1700, 1700, 5800], + ), + body(T`首次进入时,对话区显示欢迎页并提供三条常用操作建议(跑一版柔性排产、试排一版交期优先、多策略方案对比),单击即等同于在对话区发送对应指令,可作为新用户的入门路径。`), +); + +// ---------------- 三、对话式操作 ---------------- +PAGEBREAK(); +add( + h1(T`三、对话式操作`), + h2(T`3.1 对话发起排产`), + body(T`在底部输入框输入“跑一版柔性排产”并回车,系统完成求解后在对话流中返回结果卡,内容包括虚拟产线条数、工单数量、最晚完工时间、准时订单数与冲突数量。若存在缺料、工艺路线未确认等阻断项,系统会逐项列出具体问题对象与修复指引,不会以“排产完成”掩盖异常。`), + ...fig("03-对话柔性排产.png", T`对话发起柔性排产:结果卡与阻断项清单一并返回`), + tip(T`同类指令还包括“试排一版交期优先”“按产能均衡排一下”“换型最小化试排”。“试排”仅生成 DRAFT 草稿版本,不影响正式数据。`), + h2(T`3.2 多策略方案对比`), + body(T`输入“对比几种策略”,系统在隔离沙盒中并行执行交期优先、产能均衡、换型最小化、战役合并、综合优化等多种策略,并返回方案卡组。每张方案卡标注延迟、冲突、利用率、换型次数等关键指标;选定后点击“采用此方案”,该策略结果即落入主干草稿。对比过程不改变主干世界状态。`), + ...fig("19-对话策略对比.png", T`多策略对比:五种策略方案卡并排呈现,支持一键采用`), + h2(T`3.3 知识库问答`), + body(T`系统内置知识库,承载工艺规定、SOP 等业务文档。直接提问(如“换线有什么规定”)即可检索相关条款,回复中附带来源出处;检索未命中时系统明确告知无相关内容,不生成虚构条文。`), + ...fig("20-对话知识问答.png", T`知识库问答:答案附出处,未命中时如实反馈`), +); + +// ---------------- 四、柔性工作台 ---------------- +PAGEBREAK(); +add( + h1(T`四、柔性工作台`), + body(T`点击右侧工具导轨“柔性工作台”图标,展开图形化操作面板。该面板面向多品种小批量、设备可移动的生产模式:系统以工序为粒度组织资源,从具备相应工序能力的设备池中动态取用设备,为订单临时组建虚拟产线,任务完成后设备释放回池。`), + ...fig("12-柔性工作台.png", T`柔性工作台:排产、询价、异常、冲突、What-if 集成于同一面板`), + body(T`面板各功能区说明如下:`), + table3( + [T`功能区`, T`操作说明`], + [ + [T`瓶颈锚排产 / 正排 / 倒排`, T`三种排产模式:瓶颈工序锚定混合排、按开工顺序正排、按交期倒排`], + [T`三模式对比`, T`三种模式并行求解并对比结果,辅助模式选择`], + [T`滚动时域`, T`设定排产窗口:实时 60 分钟、短窗 2 小时、中窗 2 天、长窗 7 天`], + [T`分级重排`, T`异常响应幅度:L2 短窗重排、L3 日窗重排、L4 全局重排,均须经 P2 确认`], + [T`交期询价`, T`输入产品与数量,模拟插入当前排程,输出乐观 / 预计交期及资源缺口提示`], + [T`SAP 集成 / MES 执行`, T`ERP 订单与库存同步、定稿版本下发车间执行(P3 二次确认)`], + ], + [2600, 6600], + ), + tip(T`面板下方实时标注当前限制性瓶颈工序及其日产能(图示为“喷涂,日产能≈1728 件”)。系统产能以瓶颈工序为准,设备停机或调区后即时重算。`), +); + +// ---------------- 五、结果视图 ---------------- +PAGEBREAK(); +add( + h1(T`五、结果视图`), + body(T`排产产生结果后,点击工具导轨首图标“会话结果”,右侧展开视口面板,页签包括:甘特图、负荷热力、利用率、交期承诺、分桶计划、KPI、对比表、世界对比、产能池。各视图功能分述如下。`), + h2(T`5.1 产能池仪表盘`), + body(T`按工序能力池逐池展示日产能、设备数量与负载率,并提供未来 4 小时、1 天、1 周的负载预测;顶部显著位置标注当前限制性瓶颈池。设备停机、调区等变更即时触发产能重算。`), + ...fig("09-产能池.png", T`产能池仪表盘:瓶颈标注与短中长期负载预测`), + h2(T`5.2 资源利用率`), + body(T`以热力图呈现各设备逐日负载:绿色表示低负载(<50%),黄色至橙色表示负载升高,红色表示超载(≥100%)。可快速识别长期超载设备与闲置设备,评估排产均衡性。`), + ...fig("23-利用率.png", T`资源利用率:设备 7 日负载热力图`), + h2(T`5.3 KPI 仪表盘`), + body(T`汇总平均利用率、准时率、总延迟、在制工单、未解冲突、MES 完工率六类运营指标,同时覆盖固定轨与柔性轨两条排产链路。`), + ...fig("08-KPI仪表盘.png", T`运营 KPI 仪表盘:六类核心指标一屏汇总`), + h2(T`5.4 分桶计划`), + body(T`面向排产前的中长期评估:近 7 日按日、中期按周、远期按月分桶,将产能与需求放入同一时间框架对比。配套功能包括可行性分析(订单能否按期)、库存投影(断料与安全库存预警)、削峰(超载时段向空档时段的挪动建议)与产供决策(加班、扩线、外协建议)。`), + ...fig("07-分桶计划.png", T`分桶计划:粗能力评估、可行性、库存投影与削峰`), + h2(T`5.5 对比表与世界对比`), + body(T`“对比表”将多策略关键指标排列为矩阵并高亮最优项;“世界对比”将两个版本的世界状态并排呈现,逐项列出订单、资源占用与 KPI 差异,适用于方案采用前后或回滚前后的差异核查。`), + ...fig("22-世界对比.png", T`两个世界对比:版本间订单、资源与 KPI 差异逐项呈现`), + tip(T`甘特图页签在生成正式排产版本后,以时间条呈现各工单;工单支持拖拽调整(15 分钟吸附、拖拽过程实时校验),松手后生成 P2 确认卡,批准后生效。`), +); + +// ---------------- 六、订单管理 ---------------- +PAGEBREAK(); +add( + h1(T`六、订单管理`), + body(T`点击工具导轨“订单管理”图标进入。面板顶部汇总订单总数、待审、加急与预测四类计数;中部为“闭环 APS 阶段”流水线,将订单生命周期划分为六站:商务订单 → BOM/供需净算 → 制造需求准入 → 排产草案 → P2 发布 → P3 MES 下发。各站设有独立门禁,未满足准入条件时停留在对应站点,并明示缺失项。`), + ...fig("13-订单管理.png", T`订单管理:六阶段闭环流水线与站点门禁状态`), + body(T`面板同时提供新增订单、批量批准、一键清理、Excel/CSV 订单导入与排产分析报告功能。相关对话指令同样有效:“订单池”“提交审核”“批量批准”“插单快评”等。`), +); + +// ---------------- 七、主数据维护 ---------------- +add( + h1(T`七、主数据维护`), + body(T`点击工具导轨“主数据”图标进入。主数据是排产准确性的基础,面板按页签组织:资源(工厂→车间→产线→设备层级树)、工艺模型(物料 / BOM / 工序 / 工艺路线 / 产线绑定 / 换型矩阵)、柔性资源、日历与维保、工时维护。`), + ...fig("14-主数据.png", T`主数据:工艺模型页签(物料、BOM、工序、工艺路线)`), + body(T`主数据录入须遵循依赖顺序:工厂与产线 → 物料 → BOM → 工序 → 工艺路线 → 产线产品绑定,最后录入订单。任一环节缺失将导致排产失败或结果缺乏依据。全部修改均经确认卡批准后生效,且仅影响其后生成的排产版本,不回写历史版本。`), + tip(T`批量数据可通过“导入 Excel/CSV”入口处理,遵循“表头映射 → 预览校验 → 确认入库”流程,支持订单、物料、BOM、工艺路线、设备、模具等对象。`), +); + +// ---------------- 八、排产结构追溯 ---------------- +add( + h1(T`八、排产结构追溯`), + body(T`点击工具导轨“排产结构”图标,查看当前排产版本的结构化分解:每张销售订单拆解为哪些工序、各工序占用哪台设备的哪个时间段。排产结果中的每道工序均可回溯至订单、BOM 与工艺路线,保证排产依据可核查。`), + ...fig("15-排产结构.png", T`排产结构:订单—工序—设备的完整追溯链`), +); + +// ---------------- 九、多智能体编排 ---------------- +PAGEBREAK(); +add( + h1(T`九、多智能体编排`), + body(T`点击工具导轨“多智能体编排”图标进入。该功能支持以目标(Goal)驱动的方式组织多智能体协作:创建 Goal(如“锐扬排产全流程”,内置数据解析 → MRP 分解 → 下达 → 排产 → 报告五项任务)后点击“分发执行”,系统按能力边界将任务分配给相应智能体,无匹配智能体时自动创建。`), + ...fig("16-多智能体编排.png", T`多智能体编排:Goal 挂载、自动分发与看门狗监控`), + body(T`看门狗机制负责执行监控:任务超时(默认 45 秒)收回重排,多次重试失败升级为人工关注,全部任务完成后自动结案。消息总线实时呈现智能体间的任务接力;失败任务如实标记 FAILED 并升级,不伪报成功。`), +); + +// ---------------- 十、DXF 图纸识别 ---------------- +add( + h1(T`十、DXF 图纸识别`), + body(T`点击工具导轨“图纸识别”图标,上传 DXF 图纸(或填写本地路径、扫描工程目录)。系统自动解析标题栏的图号、名称、比例、版本等信息,逐字段标注置信度与来源;图纸以 SVG 形式预览,支持缩放、平移与图层开关。`), + ...fig("17-图纸识别.png", T`图纸识别:标题栏解析、置信度标注与 SVG 预览`), + body(T`解析结果生成物料候选,经人工核对并通过确认卡批准后方写入主数据。图纸未承载的字段(材质、工时、BOM 子件数量等)一律显式标记为缺失,不做推测填充。`), +); + +// ---------------- 十一、命令面板 ---------------- +add( + h1(T`十一、命令面板(Ctrl+K)`), + body(T`按下 Ctrl+K 唤出命令面板,输入关键词即可过滤全部面板与视图命令。例如输入“柔性”,即列出“柔性工作台”“柔性甘特”等条目,回车直达目标功能,是跨功能跳转的最高效方式。`), + ...fig("18-命令面板.png", T`Ctrl+K 命令面板:关键词过滤、回车直达`), +); + +// ---------------- 十二、安全与治理机制 ---------------- +PAGEBREAK(); +add( + h1(T`十二、安全与治理机制`), + table3( + [T`机制`, T`说明`], + [ + [T`门禁确认卡`, T`版本发布、采购下达、主数据修改、MES 下发等正式操作须经人工批准后执行`], + [T`自动检查点`, T`每次执行确认操作前自动创建快照;也可随时通过“存个档”手动创建`], + [T`时间线回滚`, T`点击时间线上的检查点即可恢复历史状态,回滚执行前自动再次存档`], + [T`沙盒隔离`, T`试排与策略对比在草稿或沙盒中进行,不影响主干数据`], + [T`审计哈希链`, T`全部审批与执行操作记入不可篡改的审计链,支持断链检测与倒查`], + [T`诚实阻断`, T`缺料、无可用设备等异常以红色阻断呈现并附修复入口,不输出虚假成功状态`], + ], + [2200, 7000], + ), +); + +// ---------------- 附录 ---------------- +add( + h1(T`附录:常用口令速查`), + table3( + [T`业务场景`, T`对话指令`], + [ + [T`排产`, T`试排一版交期优先 / 按产能均衡排一下 / 换型最小化试排 / 跑一版柔性排产 / 三模式对比`], + [T`方案对比与发布`, T`对比几种策略 → 采用此方案 → 发布这个版本`], + [T`订单分解`, T`执行MRP分解 / 分解订单 SO-001 / 生成采购建议 / 下达MRP建议单`], + [T`紧急插单`, T`插单快评 → 采用插单`], + [T`计划层分析`, T`时间分桶 / 可行性分析 / 能否按期 / 库存投影 / 削峰 / 产供决策`], + [T`订单管理`, T`订单池 / 提交审核 / 批量批准 / 查看预测 / 预测转正`], + [T`知识检索`, T`换线有什么规定 / @知识:标题`], + [T`报表`, T`生成日报 / 版本对比 / 打开仪表盘`], + [T`存档与回滚`, T`存个档 / 回滚`], + [T`数据管理`, T`分析一下数据文件 / 导入完整生产路线 / 清空数据重新初始化`], + ], + [2000, 7200], + ), + body(T`说明:系统对无法识别的指令会明确提示无法理解或检索未命中,不会虚构响应。遇到红色阻断时,应按修复指引补齐缺失的物料、工艺或资源后重新排产。`), +); + +const doc = new Document({ + features: { updateFields: false }, + sections: [{ + properties: { + page: { size: { width: 11906, height: 16838 }, margin: { top: 1440, bottom: 1440, left: 1440, right: 1440 } }, + }, + headers: { + default: new Header({ children: [para(run(T`工业智核 APS 计划排产智能体 · 图文使用手册`, { size: 18, color: palette.light }), { alignment: AlignmentType.CENTER, spacing: { after: 0 } })] }), + }, + footers: { + default: new Footer({ children: [para(new TextRun({ children: [PageNumber.CURRENT], size: 18 }), { alignment: AlignmentType.CENTER, spacing: { after: 0 } })] }), + }, + children, + }], +}); + +fs.writeFileSync(outputPath, await Packer.toBuffer(doc)); diff --git a/package.json b/package.json index 7e5f30c..19cff5d 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,8 @@ }, "engines": { "node": ">=22.12.0" + }, + "dependencies": { + "docx": "^9.7.1" } }