# Iaiops Pharma

> Pharmaceutical-manufacturing edition of iaiops — GMP drug/biologics plants as a distinct vertical from generic buildings and municipal water. BACnet/IP BMS+EMS (cleanroom pressure cascade, temperature/RH), Modbus (PW/WFI skids, stills, EDI, analysers), HART-IP (conductivity / TOC / level transmitters), OPC-UA (DCS, bioreactors, plant SCADA), plus the cross-protocol brain. Three signature checks: cleanroom_pressure_cascade (EU GMP Annex 1 cascade, door by door), cleanroom_particle_check and pharma_water_check (USP <645> stage-1 procedure). Use when the task mentions pharma, 制药, 药厂, GMP, cleanroom / 洁净室 / 洁净区, Annex 1, 压差梯度, pressure cascade, grade A/B/C/D, 尘埃粒子 / particle count, EMS / 环境监测, PW / WFI / 纯化水 / 注射用水, TOC, 电导率, conductivity, bioreactor / 生物反应器, 冻干机 / lyophilizer, 灌装线 / filling line, CSV, IQ/OQ, Annex 11, Part 11, or 数据完整性 / ALCOA. Read-first; this edition's tool surface is read-only.

- Skill: `industrial-aiops/iaiops-pharma` (Agent Skill)
- Install (CLI): `npx skillmds@latest add industrial-aiops/iaiops-pharma`
- Raw SKILL.md: https://api.skillmd.com/api/skills/industrial-aiops/iaiops-pharma/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: industrial-aiops (https://skillmd.com/u/industrial-aiops)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/industrial-aiops/iaiops-pharma

---


# iaiops-pharma — 制药 edition（BACnet + Modbus + HART + OPC-UA + 脑）

启动：`IAIOPS_MCP=pharma` / `iaiops-mcp-pharma`（= bacnet + modbus + hart + opcua + 脑；
等价显式写法 `IAIOPS_MCP=bacnet,modbus,hart,opcua`）。BACnet 需 extra：
`pip install iaiops[bacnet]`；HART 需 `pip install iaiops[hart]`。

> **为什么单列 edition，而不是加协议**：制药**没有专用现场协议**。洁净室跑 BACnet，
> 制水跑 Modbus/HART，灌装冻干跑 S7，DCS 跑 OPC-UA —— 全在基座里了。
> 缺的从来是**语义**：`iaiops-water` 的指标是市政口径（DO / ORP / 余氯 / 氨氮 / 浊度），
> 而 PW/WFI 看的是电导率、TOC、回路温度，判法还不一样；iaiops-clinical 的隔离病房压差检查
> 判的是**一个房间**够不够负压（那个工具不在本 edition 的表面上），Annex 1 要的是**级联链**。
> 所以这个 edition 复用同一套协议栈和同一个脑，只叠三个制药语义检查。

> **不内嵌任何药典限值。** 尘埃粒子的 grade/state/size 表、USP <645> stage-1 电导率表、
> TOC 限值 —— 都属于**贵厂在其药典版本下已确认的质量标准**。把一份本仓无人能核的转录数字
> 放进来，最后就是它在决定一批环境算不算合格，而**出错的方向恰恰是好看的那一边**
> （限值抄松了，读起来就是「符合标准」）。所以限值由调用方传入，工具把它引用回来；
> 没给的项报 `no_limit` / `not_graded` 并点名，**绝不跳过、绝不当作通过**。

## 工具

### 制药专属（edition 工具;仅随 pharma edition 加载,不进全局脑）
- `cleanroom_pressure_cascade` — **洁净区压差级联**（EU GMP Annex 1）。不是「这间够不够负压」
  （那是 clinical edition 的隔离房检查），而是**从 A 到 D 这条链成不成立**：每一道跨级别的门
  都必须由洁净侧压向次洁净侧，链条只和最弱的那道门一样强。逐门判
  `correct` / `insufficient` / `reversed` / `unknown`，worst-first，每条引用压差与要求值。
  **相邻关系由人声明**（D25）：房间清单不含拓扑，猜门就是在编造这个检查本身要验的那层关系；
  不给 `doors` 就明说「未评估级联」并给出补法，房间读数照常判。
  `min_cascade_pa` 默认 10.0（Annex 1 指导值 10–15 Pa），**以贵厂确认值为准**。
- `cleanroom_particle_check` — **尘埃粒子**对比**你传入的**限值（`{grade: {state: {size: max}}}`）。
  逐（房间, 粒径）判 `within_limit` / `exceeded` / `no_limit` / `no_reading`，worst-first，
  两个数都引用。缺限值的项**单独计数**，不混进「通过」里。
- `pharma_water_check` — **PW / WFI**。价值在**程序**不在数字，两处最常做反的：
  ① stage-1 用的是**未温度补偿**的读数，且测得温度要**向下**取到表里的档位 ——
  拿变送器 25 °C 补偿输出去比这张表，是常见错误，而且**在回路温度下是往好看的方向错**，
  所以补偿过的读数（以及**没声明**补没补偿的读数）一律拒判并说明理由；
  ② **超出 stage-1 不是不合格**，是转 Stage 2 —— 判词按药典的说法写，不写 FAIL。
  TOC 限值与回路温度带同样由你声明；没声明的报 `not_graded`。

### BACnet/IP（read-first;BMS / EMS / 洁净室压差、温湿度）
- `bacnet_discover` — Who-Is：本地 BACnet/IP 网上的设备
- `bacnet_object_list` — 设备的 object/point 列表
- `bacnet_read_property` — 单对象属性（默认 presentValue）
- `bacnet_read_points` — 所有 analog/binary/multistate 点的 presentValue（压差/温湿度快照）
- `bacnet_cov_subscribe` — 单对象有界 Change-of-Value 捕获
- `bacnet_read_trend_log` — 读设备 TrendLog 缓冲记录
- `bacnet_write_property` — **[WRITE][HIGH][MOC]** 写单对象属性（默认 `dry_run=True` + 双确认）

### Modbus-TCP / Modbus-RTU（只读；多效蒸馏水机、EDI/RO、分配撬、分析仪）
- `modbus_read_holding` `modbus_read_input` `modbus_read_coils` `modbus_read_discrete`
- `modbus_detect_byte_order` — 字节/字序自动探测（分析仪浮点数常见坑）
- `modbus_list_templates` / `modbus_apply_template` — 厂商寄存器模板 → 命名 tag
- `modbus_health_summary` — 寄存器 vs 阈值分类

### HART-IP（只读；电导率 / TOC / 液位 / 流量变送器，经网关，端口 5094）
- `hart_device_identity` — 通用设备身份（command 0）
- `hart_primary_variable` — 主变量 PV
- `hart_dynamic_variables` — PV/SV/TV/QV + 回路电流（command 3）
- `hart_burst_sample` — 主动采样 burst 变量；`hart_burst_listen` — 被动监听（待核实）

### OPC-UA（只读；DCS、生物反应器、层析/超滤、全厂 SCADA、MES 对下）
- `opcua_server_info` / `opcua_browse` / `opcua_read_node` / `opcua_read_many`
- `opcua_subscribe_sample` `opcua_read_alarms` `opcua_alarm_events`(A&C 带时间戳) `opcua_read_history`(HDA)
- `opcua_diagnose_connection` — 连接失败归因（证书/策略/认证/网络/配置）
- `opcua_discover_tags` — 自动发现 + 语义资产建模
- `opcua_health_summary` — tag vs 阈值分类；`opcua_anomaly_scan` — 有界统计异常扫描

### 跨协议脑（永远随 server 暴露）
- 诊断：`diagnose_dataflow` `downtime_root_cause` `downtime_root_cause_live` `downtime_triage`
  `learn_cause_weights` `rca_corpus_from_maintenance` `historian_health` `alarm_bad_actors` `tag_health`
  `subscription_health` `heartbeat_health` `alarm_flood_analysis` `alarm_cascade`
  `alarm_rationalization_worksheet`
- 告警事件聚类：`alarm_event_clusters` — `alarm_bad_actors` 按**来源**排名,回答的是「哪台仪表最吵」,
  不是「哪个故障最吵」;一个把同一条件写成十种说法的厂会得到十个 bad actor。这个按事件**说了什么**分组。
  合并规则是**去掉大小写/标点/数字后的精确相等,不是相似度** —— 故意做笨,所以不需要模型、且可核对;
  每个簇都列出被合并的原文与来源。**它不主张两条措辞不同的告警是同一个故障**,那由人判断。
- 数据质量：`data_quality_scorecard` `data_quality_fleet_rollup`（GMP 重点：电极老化 flatline /
  staleness / 量程外 —— 坏数据绝不静默插值，因为「插出来的数」进不了 GxP 记录）
- 分析：`oee_compute` `downtime_events` `oee_multidim` `monitor_changes`
  `health_summary` (deprecated) `anomaly_scan` (deprecated)
- 上下文基线：`baseline_learn_contextual` `baseline_check_in_context` —— 一个位号只学一条带,
  在它有不止一个「正常」时就是错的(同一台干燥机 recipe A 走 180 °C、B 走 240 °C,一条带横跨两者,
  于是两个工况都不可能出错)。**上下文由人声明,绝不推断**(D16);某个上下文历史太薄就**拒学**,
  不借用别的上下文的样本;读数落在没学过的上下文里报 `unknown_context`,**绝不回落到全局带** ——
  回落等于把「这个工况从没见过」说成「这个工况正常」。
- 上下游归因：`downtime_attribution` —— RCA 只按**时间**加权,所以一次上游停机会让每台下游设备
  各自给出一个自信的本地根因。方向来自**声明的**产线顺序(D25:产线上共现是必然,拿它挖边等于
  制造因果),顺序来自时间戳,**两者都要成立**;没声明关系就报 `not_evaluable` 并给出补法。
- 资产：`asset_inventory` `cross_protocol_asset_model` `adopt_alias_map` `diff_alias_map`
- 设备公告对照：`device_advisory_check` —— `scan` 早就在读 vendor/model/firmware,却什么都没做。
  这条把它接上,并**刻意停在漏洞扫描器会继续往前走的地方**:只报「落在公告声明的版本范围内」,
  **不说「可利用」、不给严重度** —— 可达性与补偿控制决定那件事,而只读扫描看不见它们。
  **不内置任何 CVE 库**(过期却看着像最新的库比没有更糟),由现场挂载文件、离线可用,每条必须带来源。
  读不出固件报 `version_unknown`,读得出但排不了序报 `version_unparsed` —— 都不算通过;
  公告没提到的设备**不出现在结果里**,那是「未知」不是「没有」。
- 基线：`baseline_learn` `baseline_check` `baseline_record_change` `baseline_status`
  （change-log 基线：拒学薄历史、只报持续越带、每次告警必引基线样本 —— 非黑盒异常检测）
- 合规/信创：`compliance_mapping` `compliance_frameworks` `compliance_dengbao_levels`
  `compliance_report` `compliance_evidence_bundle`
  `historian_push` `export_data` `historian_query` `historian_coverage` `stream_publish` `uns_publish` `stream_publish_event` `rca_narrate` `fleet_status` `fleet_incidents` `pdm_forecast`
- 程序解读：`plc_program_outline` `plc_program_xref` `plc_program_section` `plc_program_visibility`（解读导出的 ST/AWL/L5X 程序,只读文件,强制引用行号）
- 程序变更基线：`plc_program_snapshot` `plc_program_drift` `plc_program_history` — 把「认可的那一版」
  的结构记下来（文件 SHA-256 + 每个 block 的结构指纹：声明/调用/分支条件/定时器，**不含行号、注释、
  block 顺序**，所以在文件顶上加一行注释不会把整份程序报成变更），之后问某一次导出**动没动**。
  三个判词咬得很紧：`identical` **只**由 SHA-256 相同得出；`logic_changed` 逐 block 指出哪一类变了；
  `changed_outside_extracted_structure` = 字节变了而结构指纹全同 —— 多半是注释/排版，但这些 parser
  是结构抽取不是文法，**所以它不叫「仅文档」，也不构成放行**。删历史只在 CLI（`iaiops program forget`）：
  删变更控制证据不该离 agent 只有一次调用。存的是 block 名 + 哈希 + 计数，**不落声明、源码行和注释**。
- 自证：`verify_determinism` — 把「拿掉模型、断网、同一份数据重跑、输出逐字节相同」**跑出来**：
  固定数据集过一遍分析层，规范化后取 SHA-256，在本进程跑两遍、再在两个不同 PYTHONHASHSEED 的
  全新解释器里各跑一遍（这一臂才抓得到集合/字典迭代顺序渗进结果），全程 socket 抛异常。
  给 CSV/验证团队的是一条能写进 IQ/OQ 的测试用例，不是一句形容词。
- 元：`protocols_supported`(产品能做什么)· `site_readiness`(这个站点今天能跑什么、还差什么;零联网)
  · `onboarding_status`(这个站点走到哪一步了、下一条命令是什么;零联网)
  · `onboarding_config_draft`(把已存的 scan 变成 config.yaml 端点草稿 —— **只写连接事实**：
    `value: null` 的字段是扫描没确认的，照抄它的 `caution`，别替人填也别删；`tags` 永远是空的，
    点位语义只能人来确认 `iaiops tags export|apply`)
- 调查层（§13，八步证据闭环）：`investigation_readiness` `investigation_open` `investigation_show`
  `investigation_list` — 「真出事时这个站能走到第几步、每个缺口还差什么」，以及对一个**已过去的窗口**
  逐步走完并留档（不碰设备）。缺口分两种:**你没供**(给命令) 与 **产品供不了**。
- 产线关系与机制库：`line_relation_declare` `line_relations_list` `mechanism_library_check`
  `mechanism_library_list` — 上下游由**人声明**（D25:线上下游共现是必然，推不出因果）；
  机制库按 ISO 14224 分 mode/mechanism/cause，**可排除、绝不确认**，
  库里没有这条原因 → `nothing_known`（不是「无异议」）。

## Workflows

1. **Doctor-first**：`protocols_supported` → `iaiops doctor` → BACnet 先 `bacnet_discover`，
   OPC-UA 先 `opcua_diagnose_connection`，HART 先 `hart_device_identity`。
2. **先勘察，再取数，最后才说明**：`iaiops scan`（只读、端口白名单、零发包预览、自包含 HTML）
   出一份 BACnet/Modbus 设备清单 —— 大多数药厂拿不出一份可信的 OT 资产表，而
   62443-2-1 / NIS2 / 等保都从资产清册开始。然后 `iaiops readiness` 说明**这个站今天能跑哪些场景、
   每个缺口还差什么**。
3. **洁净区**：`bacnet_read_points` 取压差点 → 声明门的相邻关系 → `cleanroom_pressure_cascade`；
   粒子计数配上**你们的**限值 → `cleanroom_particle_check`。EMS 报警泛滥用
   `alarm_flood_analysis` + `alarm_rationalization_worksheet`（一次压差波动刷几百条、
   有用的那条被淹掉，是制药常态）。
4. **制水**：Modbus/HART 取电导率、TOC、回路温度 → `pharma_water_check`，
   记得声明 `temperature_compensated`。
5. **给 CSV / 验证团队**：`verify_determinism`（或 `iaiops verify determinism --out record.json`）——
   固定数据集、断网、两个不同 PYTHONHASHSEED 的全新解释器，输出 SHA-256。
   这是一条能写进 IQ/OQ 的测试用例：「移除模型、重跑标准数据集、比对哈希值一致」。
   拿结论的那一段**故意不让模型碰**，四层之下零模型 import，有守卫测试盯着。
6. **变更控制**：PLC 程序变更 = GMP 变更控制，未记录的变更是数据完整性缺陷。
   `plc_program_snapshot` 记下认可的那一版，`plc_program_drift` 问后来的导出动没动 ——
   注意 `changed_outside_extracted_structure` **不是放行**。
7. **MOC 写**：本 edition 的制药检查**全只读**。`bacnet_write_property` 是唯一的写口，
   `risk=HIGH` + 默认 `dry_run=True` + 改前值 undo + `iaiops approve` 具名审批双确认。
   **洁净区/生命安全相关对象未经授权绝不写。**

## 现在做不到的（说出来比假装支持有用）

| 缺口 | 影响 | 状态 |
|---|---|---|
| 没有 OSIsoft / AVEVA PI 连接器 | 药厂历史库十有八九是 PI，「tap 架在 historian 旁边」这句话在现场落不了地 | ❌ 未做；接入路径有商业/许可门槛，**待核实** |
| S7comm 无真机验证 | 灌装线/冻干机以西门子为主 | ⚠️ `pyS7` 已接入，真机 **待核实**（且 S7 不在本 edition 的默认 profile 里，需显式加 `s7`） |
| BACnet live HVAC / COV / 写路径 | 只读采集没问题，写与 COV 订阅未在真实 BMS 上验证 | ⚠️ **待核实** |
| 没有 GxP 对照（Annex 11 / Part 11 / Annex 22） | 现有的是《工控网络安全防护指南》/ 等保 2.0 / IEC 62443 | ❌ 未做 |
| LIMS / QMS / MES | **不做**，且是有意的：它们走 REST/DB 不走现场协议，且一碰就进 Part 11 电子记录范围 | ⛔ 超出范围 |

## 支持版本矩阵（内部 HLD §8，设计文档不随本仓发布；`待核实` 不得当既成事实）

| 协议 | 库(pin) | 规范/版本 | 覆盖 | 传输 | 自测 |
|---|---|---|---|---|---|
| BACnet/IP | `BAC0>=2023.6,<2026`（其下为 bacpypes3） | BACnet/IP (ASHRAE 135) | BMS/EMS 控制器、压差/温湿度点 | UDP/47808 | ✅ 进程内虚拟设备；live HVAC/COV/写 待核实 |
| Modbus-TCP | `pymodbus>=3.5,<4` | App 1.1b3；FC 1/2/3/4/5/6/15/16 | 制水撬/分析仪/任意 TCP 从站 | TCP/502 | ✅ |
| Modbus-RTU | `pymodbus>=3.5,<4` + `pyserial>=3.5` | Modbus serial (RTU) | 串口从站 | RS-485/serial | ✅ socat PTY；物理 RS-485 待核实 |
| HART-IP | `hart-protocol>=2023.6,<2025`（extra） | HART-IP（经网关） | 电导率/TOC/液位/流量变送器 | UDP/TCP 5094 | ⚠️ codec CI 自测；真机网关 待核实 |
| OPC-UA | `asyncua>=2.0,<3` | OPC UA 1.0x（DA+HA+AC 子集） | DCS / 反应器 / SCADA | opc.tcp | ✅ 进程内 + 证书 Sign/SignAndEncrypt；第三方 server + X509 用户令牌 待核实 |

