feat: 完成 issue #16 [M1] 编制 DCS 点表需求模板(点位/量纲/采样率字段规范)

This commit is contained in:
2026-08-04 22:30:54 +08:00
parent 33c2586dd7
commit e0ed2c58e6
2 changed files with 218 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
# DCS 点表需求模板(点位 / 量纲 / 采样率字段规范)
> 对应 EPIC #2「[M1] 数据准备」子任务 **#16**(0.5d):
> 编制向客户 DCS 系统收集点位需求的标准化模板,字段规范对齐
> PRD 5.1「① 边缘采集网关」CSV 字段规范与内核点位字典 schema
> (`core/edge-gateway/config/point_dict.example.csv`)。
>
> 交付后由实施工程师按本模板收集客户点表 → 配置台导入校验 →
> 推送边缘网关加载只读采集(PRD 5.1 用户操作流程)。
---
## 1. 文档目的
在 M1 数据准备阶段,向客户(车间/仪表/自动化人员)收集 **DCS/PLC 点位需求**,
作为点位字典(`point_dict.csv`)的输入来源。本模板统一:
- **点位**(point):需要采集的工艺测点(温度/压力/流量/液位/状态等);
- **量纲**(unit):每个测点的计量单位(℃/kPa/m³/h/%/Hz 等);
- **采样率**(sampleRate):每个测点的采集周期(毫秒)。
字段规范与平台内核**一一对应**,客户按模板填写后可直接导入校验,避免
人工翻译造成的字段漂移。
---
## 2. DCS 系统对接信息(每套系统填写一份)
| 字段 | 说明 | 示例 |
| --- | --- | --- |
| 系统名称 | DCS/PLC 系统名称 | 沸腾氯化车间和利时 DCS |
| 厂家/型号 | 设备厂家与系列 | 和利时 DCS / 西门子 S7-1200 |
| 软件版本 | 系统软件版本 | HOLLiAS-M 3.2 |
| 数据接口 | 支持的数据接口 | OPC UA / OPC DA / S7 协议 |
| OPC 服务器 | OPC 服务器地址(如可用) | opc.tcp://10.20.1.10:4840 |
| 网络隔离要求 | 采集网与办公网隔离方式 | 单向网闸 / 只读镜像(见 #27) |
| 对接负责人 | 客户侧接口人 | 王工(仪表科) |
> 采集侧**严格只读、不反控**(PRD 5.1 验收:零控制指令下发),
> 网络拓扑隔离方案见子任务 #19「现场勘查采集网/工业环网/办公网拓扑与隔离策略」。
---
## 3. 点位需求表字段规范
客户按下表逐列填写(**表头与内核点位字典 CSV 完全一致**,可整表复制为 CSV):
| 字段 | 类型 | 约束 | 说明 | 示例 |
| --- | --- | --- | --- | --- |
| device_id | string | 必填,唯一 | 设备编号(车间-设备) | CLF-01 |
| point_id | string | 必填,唯一 | 测点编号(设备.测点) | CLF-01.TEMP |
| name | string | 必填 | 测点中文名 | 炉温 |
| unit | enum | 必填 | 量纲(见 §4 量纲字典) | ℃ |
| dataType | enum | 必填 | float / int / bool | float |
| sampleRate | int | 必填,>0 | 采集周期(毫秒) | 1000 |
| qualityCode | bool | 默认 true | 是否启用质量码 | true |
| opcNode | string | 选填 | OPC UA 节点路径 / PLC 地址 | ns=2;s=CLF.Temp |
| protocol | enum | 必填 | opcua / s7 / modbus / weighing / energy / manual | opcua |
**字段说明**
- `device_id`:建议「车间/系统前缀 + 序号」,如 `CLF-01`(氯化炉 1#)、`S7-01`(PLC 站 1#);
- `point_id`:全局唯一,建议「`device_id.测点缩写`」,如 `CLF-01.TEMP`;
- `sampleRate`:按测点重要性分级填写(见 §5 采样率分级);
- `opcNode`:OPC UA 用节点路径(`ns=2;s=...`);S7 用数据块地址(`DB100.0.0`);
称重/能源表用寄存器地址(`holding:40001`);人工录入(manual)可留空;
- `qualityCode`:开启质量码后,采集侧附带质量位(坏值/不确定值),平台按质量位过滤。
---
## 4. 量纲字典(unit 枚举)
| 类别 | 允许值 |
| --- | --- |
| 温度 | ℃ |
| 压力 | kPa、MPa、Pa |
| 流量 | m³/h、t/h、L/min、m³/s |
| 液位/料位 | %、m、mm |
| 转速/频率 | rpm、Hz |
| 电气 | kW、kWh、A、V |
| 重量 | kg、t、g |
| 质量指标 | %、mmol/g(化验人工录入) |
| 状态 | bool(0/1、开/关) |
> 不在上表的单位需在「备注」列说明并请平台配置台确认后再扩展。
---
## 5. 采样率分级建议(sampleRate)
| 级别 | 周期 | 适用测点 |
| --- | --- | --- |
| 高速 | 100 ms | 关键工艺控制点(炉温、炉压) |
| 常规 | 1000 ms(1 Hz) | 一般工艺参数(流量、液位、转速)——PRD 5.1 验收基线:600 点位 1Hz |
| 低频 | 5000–10000 ms | 缓慢变化量(储罐液位、环境温度) |
| 化验/人工 | 60000 ms 或按批次 | 质量指标(交联度、交换容量、钛纯度)等人工录入或 LIMS 对接 |
> 采样率需综合点位数量与链路容量评估:验收目标为 P99 ≤ 1.8s、丢失率 ≤ 0.02%(PRD 5.1),
> 点位过多时可先按「关键点位」子集接入,其余低频轮询。
---
## 6. 填表示例(Template-Ti 一期 · 氯化车间)
| device_id | point_id | name | unit | dataType | sampleRate | qualityCode | opcNode | protocol |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| CLF-01 | CLF-01.TEMP | 炉温 | ℃ | float | 1000 | true | ns=2;s=CLF.Temp | opcua |
| CLF-01 | CLF-01.PRES | 炉压 | kPa | float | 1000 | true | ns=2;s=CLF.Pres | opcua |
| CLF-01 | CLF-01.FEED | 进料量 | t/h | float | 1000 | true | ns=2;s=CLF.Feed | opcua |
| S7-01 | S7-01.PUMP_A | 泵A频率 | Hz | float | 1000 | true | DB100.0.0 | s7 |
| W-01 | W-01.WT | 称重值 | kg | float | 1000 | true | holding:40001 | weighing |
| E-01 | E-01.PWR | 电表功率 | kW | float | 1000 | true | holding:40010 | energy |
| LAB-01 | LAB-01.TI_PURITY | 钛纯度 | % | float | 60000 | true | —(人工录入) | manual |
---
## 7. 填写规范与常见错误
| 规范 | 常见错误 | 处理 |
| --- | --- | --- |
| `point_id` 全局唯一 | 跨设备重复命名 | 配置台导入校验拒绝并提示 |
| `unit` 使用量纲字典 | 自定义单位(如 "度") | 归一化为 ℃ 或先申请扩展 |
| `sampleRate` 为大于 0 的整数 | 空值/0/小数 | 校验拒绝,按分级建议补填 |
| `device_id` 与现场标识一致 | 随意编号 | 与客户确认设备位号后再填 |
| 重复/冗余测点 | 同一物理量多张表重复 | 合并,保留唯一 `point_id` |
| 高危信号(联锁/停机) | 误标为普通测点 | 平台按「只读采集 + 告警」处理,严禁反控 |
---
## 8. 交付与校验
1. 实施工程师向客户发出本模板(可与《数据准备清单》#18 同步发出);
2. 客户按 §2–§6 填写后回传(Excel/CSV 均可);
3. 配置台**导入校验**:设备/测点完整性、字段类型、单位枚举、采样率合法性;
4. 校验通过 → 生成点位字典 CSV → 推送边缘网关加载 → 启动只读采集;
5. 采集健康度(丢失率/P99)实时回传驾驶舱。
> 若客户点表延迟或字段不完整:先启用**缺省通用点位集**(EPIC #20)演示,
> 仅接入已确认点位(PRD 13 章数据依赖风险缓解策略)。
+79
View File
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
"""DCS 点表需求模板(docs/DCS点表需求模板.md)与内核点位字典 schema 一致性检查。
检查项:
1. 文档 §3「点位需求表字段规范」表中的字段名(第一列)与内核
`core/edge-gateway/config/point_dict.example.csv` 表头**完全一致**;
2. 文档中的量纲字典与填表示例单位均在内核示例出现过的类别内(人工抽查,仅告警);
3. 文档引用到的内核文件路径存在。
用法:python _check_dcs_template.py
"""
import csv
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.dirname(HERE)
DOC_PATH = os.path.join(HERE, "DCS点表需求模板.md")
CSV_PATH = os.path.join(REPO_ROOT, "core", "edge-gateway", "config",
"point_dict.example.csv")
def doc_field_table_columns() -> list:
"""抽取文档 §3 字段规范表的字段名(Markdown 表格第一列)。"""
lines = [ln.strip() for ln in open(DOC_PATH, "r", encoding="utf-8").readlines()]
fields = None
i = 0
while i < len(lines):
line = lines[i]
if line.startswith("|") and line.strip("|").split("|")[0].strip() == "字段":
header_cells = [c.strip() for c in line.strip("|").split("|")]
if "类型" not in header_cells: # §3 特征:字段规范表含"类型"列
i += 1
continue
sep = lines[i + 1] if i + 1 < len(lines) else ""
if all(re.fullmatch(r":?-{3,}:?", c.strip())
for c in sep.strip("|").split("|")):
j = i + 2
rows = []
while j < len(lines) and lines[j].startswith("|"):
cells = [c.strip() for c in lines[j].strip("|").split("|")]
if cells and cells[0]:
rows.append(cells[0])
j += 1
fields = rows
break
i += 1
return fields or []
def main() -> int:
failures = []
# 1) 文档字段规范与内核 CSV 表头一致
with open(CSV_PATH, "r", encoding="utf-8") as fh:
csv_header = [c.strip() for c in next(csv.reader(fh))]
doc_fields = doc_field_table_columns()
if doc_fields != csv_header:
failures.append(
f"文档字段 {doc_fields} 与内核 CSV 表头 {csv_header} 不一致")
# 2) 文档引用文件存在
for ref in ("core/edge-gateway/config/point_dict.example.csv",):
if not os.path.isfile(os.path.join(REPO_ROOT, ref)):
failures.append(f"引用文件缺失:{ref}")
if failures:
print("FAIL")
for f in failures:
print(" -", f)
return 1
print(f"OK: 文档字段规范与内核 schema 一致({len(csv_header)} 列),引用完整")
return 0
if __name__ == "__main__":
sys.exit(main())