- 点位字典 CSV schema/加载/自动校验(缺失字段/量纲/重复点号,PRD 5.1) - 协议可插拔只读驱动:OPC UA(S7-1200 适配)/S7/Modbus/称重/能源/模拟 - 周期采集引擎:只读+背压保护+健康度指标(丢失率/P99/可用性) - Kafka 流式上行 + 本地 spool 断点续传(丢失率≤0.02% 保障) - 模板配置外置(gateway.yaml + 点位字典 CSV),换行业零改码 - 18 个单元测试全绿;端到端运行 SLA 达标
95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""点位字典 CSV 加载器:CSV → 内存模型(Point 记录列表)。
|
|
|
|
复用化工 AI 边缘网关的点位字典机制,改为模板化读取:
|
|
- 表头必须与 schema.CSV_HEADERS 一致(列顺序不重要,按列名匹配)。
|
|
- 行为宽松:缺失列/多余列由校验器(validator.py)统一报告,加载器不做丢弃。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from . import schema
|
|
|
|
|
|
@dataclass
|
|
class Point:
|
|
"""单条测点记录(对应点位字典 CSV 一行)。"""
|
|
|
|
device_id: str
|
|
point_id: str
|
|
name: str
|
|
unit: str
|
|
data_type: str
|
|
sample_rate: int
|
|
quality_code: bool = True
|
|
opc_node: Optional[str] = None
|
|
row_number: int = 0 # CSV 行号(从 2 开始,表头为第 1 行),用于报错定位
|
|
|
|
@property
|
|
def topic(self) -> str:
|
|
"""Kafka 上行 topic(模板化:按设备聚合)。"""
|
|
return f"{self.device_id}.points"
|
|
|
|
|
|
class PointDict:
|
|
"""点位字典内存模型。"""
|
|
|
|
def __init__(self, points: List[Point]):
|
|
self.points = points
|
|
|
|
def by_point_id(self) -> Dict[str, Point]:
|
|
return {p.point_id: p for p in self.points}
|
|
|
|
def by_device_id(self) -> Dict[str, List[Point]]:
|
|
grouped: Dict[str, List[Point]] = {}
|
|
for p in self.points:
|
|
grouped.setdefault(p.device_id, []).append(p)
|
|
return grouped
|
|
|
|
def __len__(self) -> int:
|
|
return len(self.points)
|
|
|
|
|
|
def _to_bool(raw: str) -> bool:
|
|
"""宽松解析布尔列(true/false/1/0/yes/no,大小写不敏感)。"""
|
|
return raw.strip().lower() in ("1", "true", "yes", "y", "on")
|
|
|
|
|
|
def load_point_dict_csv(path: str) -> PointDict:
|
|
"""从 CSV 文件加载点位字典。
|
|
|
|
Args:
|
|
path: CSV 文件路径(UTF-8,含表头,表头列名对齐 schema.CSV_HEADERS)。
|
|
|
|
Returns:
|
|
PointDict:点位内存模型。结构/取值问题不在此抛出,
|
|
统一由 validator.validate_point_dict() 报告(便于聚合展示全部错误)。
|
|
"""
|
|
points: List[Point] = []
|
|
with open(path, "r", encoding="utf-8-sig") as fh:
|
|
reader = csv.DictReader(fh)
|
|
fieldnames = list(reader.fieldnames or [])
|
|
for row_number, row in enumerate(reader, start=2):
|
|
sample_rate_raw = (row.get("sampleRate") or "").strip()
|
|
try:
|
|
sample_rate = int(float(sample_rate_raw)) if sample_rate_raw else 0
|
|
except ValueError:
|
|
sample_rate = 0
|
|
points.append(
|
|
Point(
|
|
device_id=(row.get("device_id") or "").strip(),
|
|
point_id=(row.get("point_id") or "").strip(),
|
|
name=(row.get("name") or "").strip(),
|
|
unit=(row.get("unit") or "").strip(),
|
|
data_type=(row.get("dataType") or "").strip(),
|
|
sample_rate=sample_rate,
|
|
quality_code=_to_bool(row["qualityCode"]) if (row.get("qualityCode") or "").strip() else True,
|
|
opc_node=((row.get("opcNode") or "").strip() or None),
|
|
row_number=row_number,
|
|
)
|
|
)
|
|
return PointDict(points)
|