|
@@ -1,11 +1,13 @@
|
|
|
"""
|
|
"""
|
|
|
RO CIP 预测模型适配器。支持每日定时 + 异常触发两种运行模式。
|
|
RO CIP 预测模型适配器。支持每日定时 + 异常触发两种运行模式。
|
|
|
|
|
+拉取最近 cycle_days 天的实时在线数据,通过 minute 间隔 API 查询。
|
|
|
"""
|
|
"""
|
|
|
import logging
|
|
import logging
|
|
|
import time
|
|
import time
|
|
|
-from datetime import datetime
|
|
|
|
|
|
|
+from datetime import datetime, timedelta
|
|
|
from typing import List, Optional
|
|
from typing import List, Optional
|
|
|
|
|
|
|
|
|
|
+import numpy as np
|
|
|
import pandas as pd
|
|
import pandas as pd
|
|
|
|
|
|
|
|
from core.config import PipelineConfig
|
|
from core.config import PipelineConfig
|
|
@@ -14,6 +16,17 @@ from core.shared_state import SharedState
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
+# RO CIP 预测所需的 PLC 传感器点位
|
|
|
|
|
+_RO_PLC_POINTS = [
|
|
|
|
|
+ "RO1_FluxF", "RO2_FluxF", "RO3_FluxF", "RO4_FluxF",
|
|
|
|
|
+ "C.M.RO1_DB@DPT_1", "C.M.RO2_DB@DPT_1", "C.M.RO3_DB@DPT_1", "C.M.RO4_DB@DPT_1",
|
|
|
|
|
+ "C.M.RO1_DB@DPT_2", "C.M.RO2_DB@DPT_2", "C.M.RO3_DB@DPT_2", "C.M.RO4_DB@DPT_2",
|
|
|
|
|
+ "C.M.RO_TT_ZJS@out",
|
|
|
|
|
+ "C.M.RO_Cond_ZJS@out",
|
|
|
|
|
+ "C.M.RO_PH_ZJS@out",
|
|
|
|
|
+ "C.M.RO_ORP_ZJS@out",
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
|
|
|
class ROCIPAdapter:
|
|
class ROCIPAdapter:
|
|
|
def __init__(
|
|
def __init__(
|
|
@@ -31,7 +44,6 @@ class ROCIPAdapter:
|
|
|
# 导入 RO 模型函数(不触发 __main__)
|
|
# 导入 RO 模型函数(不触发 __main__)
|
|
|
import importlib
|
|
import importlib
|
|
|
import sys
|
|
import sys
|
|
|
- from pathlib import Path
|
|
|
|
|
|
|
|
|
|
ro_root = str(config.ro_root / "cip_predict")
|
|
ro_root = str(config.ro_root / "cip_predict")
|
|
|
if ro_root not in sys.path:
|
|
if ro_root not in sys.path:
|
|
@@ -40,7 +52,48 @@ class ROCIPAdapter:
|
|
|
ro_module = importlib.import_module("run_this")
|
|
ro_module = importlib.import_module("run_this")
|
|
|
self._process_unit = ro_module.process_unit
|
|
self._process_unit = ro_module.process_unit
|
|
|
self._main = ro_module.main
|
|
self._main = ro_module.main
|
|
|
- self._load_history = ro_module.load_history
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # ==================== 数据拉取 ====================
|
|
|
|
|
+
|
|
|
|
|
+ def _fetch_cycle_data(self) -> pd.DataFrame:
|
|
|
|
|
+ """从 API 拉取最近 cycle_days 天的 RO 数据(minute 间隔,5 分钟降采样)。"""
|
|
|
|
|
+ cycle_days = self.config.get("ro.cycle_days", 30)
|
|
|
|
|
+ end_time = datetime.now()
|
|
|
|
|
+ start_time = end_time - timedelta(days=cycle_days)
|
|
|
|
|
+ resample_interval = 300 # 5 分钟
|
|
|
|
|
+
|
|
|
|
|
+ time_index = pd.date_range(start=start_time, end=end_time, freq=f"{resample_interval}s")
|
|
|
|
|
+ result = {"time": time_index}
|
|
|
|
|
+
|
|
|
|
|
+ success, empty = 0, 0
|
|
|
|
|
+ for pt in _RO_PLC_POINTS:
|
|
|
|
|
+ raw = self.data_provider.query_single_point_history(
|
|
|
|
|
+ pt, start_time, end_time, interval="minute"
|
|
|
|
|
+ )
|
|
|
|
|
+ if raw.empty:
|
|
|
|
|
+ result[pt] = np.nan
|
|
|
|
|
+ empty += 1
|
|
|
|
|
+ else:
|
|
|
|
|
+ resampled = raw.resample(f"{resample_interval}s").mean()
|
|
|
|
|
+ resampled = resampled.reindex(
|
|
|
|
|
+ time_index,
|
|
|
|
|
+ method="nearest",
|
|
|
|
|
+ tolerance=pd.Timedelta(seconds=resample_interval),
|
|
|
|
|
+ )
|
|
|
|
|
+ result[pt] = resampled.ffill().bfill()
|
|
|
|
|
+ success += 1
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(f"RO 数据拉取: {success}/{len(_RO_PLC_POINTS)} 成功, 范围 {cycle_days} 天")
|
|
|
|
|
+
|
|
|
|
|
+ df = pd.DataFrame(result)
|
|
|
|
|
+ df["time"] = pd.to_datetime(df["time"])
|
|
|
|
|
+
|
|
|
|
|
+ sensor_cols = [c for c in _RO_PLC_POINTS if c in df.columns]
|
|
|
|
|
+ df = df.dropna(subset=sensor_cols, how="all").reset_index(drop=True)
|
|
|
|
|
+
|
|
|
|
|
+ return df
|
|
|
|
|
+
|
|
|
|
|
+ # ==================== 预测 ====================
|
|
|
|
|
|
|
|
def run_prediction(
|
|
def run_prediction(
|
|
|
self,
|
|
self,
|
|
@@ -54,9 +107,42 @@ class ROCIPAdapter:
|
|
|
tmp_limit = tmp_limit or self.config.ro_tmp_limit
|
|
tmp_limit = tmp_limit or self.config.ro_tmp_limit
|
|
|
dpt_type = dpt_type or self.config.ro_dpt_type
|
|
dpt_type = dpt_type or self.config.ro_dpt_type
|
|
|
|
|
|
|
|
- result_df = self._main(units, tmp_limit, dpt_type, df=df)
|
|
|
|
|
|
|
+ if df is None:
|
|
|
|
|
+ df = self._fetch_cycle_data()
|
|
|
|
|
+
|
|
|
|
|
+ if df.empty or len(df) < 10:
|
|
|
|
|
+ logger.warning(f"RO 数据不足 ({len(df)} 行), 跳过本轮预测")
|
|
|
|
|
+ return pd.DataFrame()
|
|
|
|
|
+
|
|
|
|
|
+ # 按机组过滤非生产数据后逐个预测(避免 flux≈0 导致 R1=∞ 传播)
|
|
|
|
|
+ results = []
|
|
|
|
|
+ for u in units:
|
|
|
|
|
+ unit_df = df.copy()
|
|
|
|
|
+ flux_col = f"{u}_FluxF"
|
|
|
|
|
+ if flux_col in unit_df.columns:
|
|
|
|
|
+ unit_df = unit_df[unit_df[flux_col] > 0.1]
|
|
|
|
|
+
|
|
|
|
|
+ if len(unit_df) < 10:
|
|
|
|
|
+ results.append({
|
|
|
|
|
+ "unit": u, "status": "ERROR",
|
|
|
|
|
+ "error_type": "INSUFFICIENT_PRODUCTION_DATA",
|
|
|
|
|
+ })
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ res = self._process_unit(u, unit_df, tmp_limit, dpt_type)
|
|
|
|
|
+ if res is None:
|
|
|
|
|
+ results.append({
|
|
|
|
|
+ "unit": u, "status": "ERROR",
|
|
|
|
|
+ "error_type": "MODEL_FIT_FAILED",
|
|
|
|
|
+ })
|
|
|
|
|
+ elif res.get("status") == "ERROR":
|
|
|
|
|
+ results.append(res)
|
|
|
|
|
+ else:
|
|
|
|
|
+ res["status"] = "OK"
|
|
|
|
|
+ results.append(res)
|
|
|
|
|
+
|
|
|
|
|
+ result_df = pd.DataFrame(results)
|
|
|
|
|
|
|
|
- # 发布正常/异常结果
|
|
|
|
|
for _, row in result_df.iterrows():
|
|
for _, row in result_df.iterrows():
|
|
|
row_dict = row.to_dict()
|
|
row_dict = row.to_dict()
|
|
|
if row_dict.get("status") == "ERROR":
|
|
if row_dict.get("status") == "ERROR":
|
|
@@ -78,6 +164,8 @@ class ROCIPAdapter:
|
|
|
})
|
|
})
|
|
|
return result_df
|
|
return result_df
|
|
|
|
|
|
|
|
|
|
+ # ==================== 异常联动 ====================
|
|
|
|
|
+
|
|
|
def _has_ro_anomaly(self, diag_result: dict) -> bool:
|
|
def _has_ro_anomaly(self, diag_result: dict) -> bool:
|
|
|
"""检查诊断结果中是否包含 RO 段异常。"""
|
|
"""检查诊断结果中是否包含 RO 段异常。"""
|
|
|
if diag_result.get("status") != "abnormal":
|
|
if diag_result.get("status") != "abnormal":
|
|
@@ -88,6 +176,8 @@ class ROCIPAdapter:
|
|
|
return True
|
|
return True
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
|
|
+ # ==================== 调度 ====================
|
|
|
|
|
+
|
|
|
def run_scheduler(self):
|
|
def run_scheduler(self):
|
|
|
"""每日定时 + RO 异常触发调度。"""
|
|
"""每日定时 + RO 异常触发调度。"""
|
|
|
last_run_date = None
|
|
last_run_date = None
|