Browse Source

feat(DHU): 新增模型监控功能并支持SDHU设备类型

新增了 `monitor.py` 模块以实现模型监控逻辑,并在 `config_reader.py` 中增加了
`get_point_info` 方法用于获取测点信息。同时扩展了对 SDHU 类型设备的支持,包括
优化、训练和预测模块中的设备类型判断与处理逻辑。

此外还完善了以下内容:
- 在 `optimize.py` 和 `train.py` 中区分 DHU 与 SDHU 设备的模型加载及优化策略
- 调整了 SDHU 模型的 waste 计算方式与输入参数
- 修复部分字段引用错误(如 DoutA → DoutP)
- 房间露点预测中增加设备运行状态检查
- 增加 Lowess 平滑函数提升房间模型预测稳定性
zhangshenhao 4 months ago
parent
commit
03394139ce

+ 17 - 0
apps/DHU/config_reader.py

@@ -76,6 +76,23 @@ class ConfigReader:
         
         return point_map
     
+    def get_point_info(self,equp_name,point_name) -> dict:
+        self.check_equp_is_exist(equp_name)
+        equp_type = self.get_equp_info(equp_name,'设备类型',info_type='str')
+        point_info = (
+            self.point
+            .loc[lambda dt:dt.设备类型.str.split(',',expand=False).apply(lambda lst: equp_type in lst)]
+            .loc[lambda dt:dt.编号==point_name]
+        )
+        if len(point_info) == 0:
+            raise Exception(f'未找到{equp_name}的{point_name}信息')
+        elif len(point_info) > 1:
+            raise Exception(f'{equp_name}下存在多个{point_name}信息')
+        else:
+            point_info = point_info.to_dict(orient='list')
+            point_info = {k:v[0] for k,v in point_info.items() if k not in self.all_equp_names_total}
+        return point_info
+    
     def get_app_info(
         self,
         equp_name: str,

+ 10 - 0
apps/DHU/model_func.py

@@ -0,0 +1,10 @@
+import os
+import joblib
+import warnings
+
+warnings.filterwarnings('ignore')
+path = os.path.dirname(os.path.realpath(__file__)).replace('\\','/')
+
+MOD_NAME = '/model.pkl'
+model    = joblib.load(path + MOD_NAME)
+

+ 148 - 0
apps/DHU/monitor.py

@@ -0,0 +1,148 @@
+import os
+from datetime import datetime,timedelta
+
+import pandas as pd
+from workflow_utils import send_back_monitor_data
+from workflow_utils import get_model_version_file_v2
+
+from .config_reader import ConfigReader
+from ...tools.data_loader import DataLoader
+from ..._utils.data_summary import print_dataframe,summary_dataframe
+from ...model._base._update_utils import update
+from ..DHU.optimize import load_model
+
+NOW             = datetime.now().replace(minute=0,second=0,microsecond=0)
+PATH            = os.path.dirname(os.path.realpath(__file__)).replace('\\','/')
+MODEL_FUNC_PATH = f'{PATH}/model_func.py'
+MODEL_FILE_PATH = f'./model.pkl'
+
+def monitor(*inputs,config=None):
+    config = {} if config is None else config
+    
+    if '__LOCAL' in config.keys():
+        config_reader_path = config['__LOCAL']
+        data_URL           = config['__URL']
+    else:
+        config_reader_path = '/mnt/workflow_data'
+        data_URL           = 'http://basedataportal-svc:8080/data/getpointsdata'
+        
+    config_reader = ConfigReader(path=f'{config_reader_path}/DHU配置.xlsx')
+    
+    for each_eaup_name,each_eaup_name_short in zip(
+        config_reader.all_equp_names,
+        config_reader.all_equp_names_short
+    ):
+        try:
+        
+            each_equp_type = config_reader.get_equp_info(each_eaup_name,key='设备类型',info_type='str')
+            data           = load_data(each_eaup_name,config_reader,config_reader_path,data_URL)
+            model          = load_model(each_eaup_name,each_equp_type,config_reader,config_reader_path,False)
+            if each_equp_type in ['DHU_A','DHU_B']:
+                model.find_F_air_val_rw(data,data,plot=False)
+            predict        = model.predict_system(data)
+            data.loc[data.State.values==0,:] = 0
+            
+            need_init = config_reader.get_app_info(each_eaup_name,'模型监控','初始化监控','bool')
+            if need_init:
+                init_monitor(each_eaup_name,config_reader,predict,model)
+            
+            _,model_version_id = get_model_version_file_v2(
+                model_id = config_reader.get_equp_info(each_eaup_name,'模型编号','int'),
+                filename = 'model_func.py'
+            )
+            push_list = []
+            for monitor_point_name in config_reader.get_equp_point(each_eaup_name,'B'):
+                if monitor_point_name not in predict.columns:
+                    continue
+                pred_value = predict.loc[:,monitor_point_name].values
+                real_value = data.loc[:,monitor_point_name].values
+                index      = data.index
+                index      = [ts.to_pydatetime().strftime("%Y-%m-%d %H:%M:%S") for ts in index]
+                
+                monitor_info = {
+                    'md_version_id': model_version_id,
+                    'ports_out'    : []
+                }
+                for idx,ts in enumerate(index):
+                    monitor_info['ports_out'].append(
+                        {
+                            'point_id': monitor_point_name,
+                            'ts'      : ts,
+                            'wf_value': float(pred_value[idx]),
+                            'ac_value': float(real_value[idx])
+                        }
+                    )
+                push_list.append(monitor_info)
+            
+            success = send_back_monitor_data(push_list)
+            if success:
+                print('推送成功')
+            else:
+                print('push_list',push_list)
+                raise Exception('推送失败')
+        
+        except Exception as e:
+            print(e)
+            raise
+
+def load_data(
+    each_eaup_name,
+    config_reader,
+    config_reader_path,
+    data_URL,
+):
+    data_input_point = config_reader.get_equp_point(
+        each_eaup_name,equp_class=['A','B'])
+    data = (
+        DataLoader(
+            path          = f'{config_reader_path}/data/monitor/data_cur/',
+            start_time    = NOW - timedelta(minutes=60),
+            end_time      = NOW,
+            print_process = False
+        )
+        .download_equp_data(
+            equp_name   = each_eaup_name,
+            point       = data_input_point,
+            url         = data_URL,
+            clean_cache = True
+        )
+        .get_equp_data(
+            equp_name = each_eaup_name,
+        )
+    )
+    summary_dataframe(data,f'{each_eaup_name}除湿机数据')
+    return data
+
+
+def init_monitor(
+    each_eaup_name,
+    config_reader:ConfigReader,
+    predict,
+    model
+):
+    pd.to_pickle({},MODEL_FILE_PATH)
+    model_info = {}
+    point_monitor = config_reader.get_equp_point(each_eaup_name,'B')
+    for each_point_name in point_monitor:
+        if each_point_name not in predict.columns:
+            continue
+        if each_point_name not in model.model_observe_data_columns:
+            continue
+        each_point_info = config_reader.get_point_info(each_eaup_name,each_point_name)
+        model_info[each_point_name] = {
+            'metric'     : {'MAE':10,'MAPE':1},
+            'point_id'   : each_point_name,
+            'point_class': each_point_name,
+            'point_name' : f'{each_point_info["部件"]}_{each_point_info["名称"]}',
+            'thre_mae'   : 10,
+            'thre_mape'  : 1,
+            'thre_days'  : 1,
+        }
+    update(
+        version_id      = 1,
+        model_id        = config_reader.get_equp_info(each_eaup_name,'模型编号','str'),
+        model_info      = model_info,
+        update_method   = 'update',
+        MODEL_FUNC_PATH = MODEL_FUNC_PATH,
+        MODEL_FILE_PATH = MODEL_FILE_PATH,
+    )

+ 122 - 28
apps/DHU/optimize.py

@@ -2,10 +2,12 @@ import os
 from datetime import datetime,timedelta
 from pathlib import Path
 from pprint import pprint
+from typing import Union
 
 import pandas as pd
 
 from ...model.DHU.DHU_AB import DHU_AB
+from ...model.DHU.SDHU_AB import SDHU_AB
 from .config_reader import ConfigReader
 from ...tools.data_loader import DataLoader
 from ..._utils.data_summary import print_dataframe,summary_dataframe
@@ -25,6 +27,7 @@ def optimize(*inputs,config=None):
     
     ALL_RESULT = {
         'EXCEPTION':{
+            'State'    : {},
             'Mod'      : {},
             'Data_ATD' : {},
             'Opt'      : {},
@@ -33,7 +36,7 @@ def optimize(*inputs,config=None):
         'STATUS':{
             'Mode_Steady': [],
             'Mode_Low'   : [],
-            'Mode_stop'  : []
+            'Mode_Off'   : []
         }
     }
     ALL_OPT_RES = []
@@ -60,11 +63,29 @@ def optimize(*inputs,config=None):
         NOW = config_reader.get_app_info(each_eaup_name,'实时优化','运行时间','datetime').replace(second=0,microsecond=0)
         print(f'{each_eaup_name}开始实时优化,设备类型为{each_equp_type},运行时间为{NOW}')
         
+        try:
+            dhu_State = load_dhu_State(
+                each_eaup_name     = each_eaup_name,
+                config_reader      = config_reader,
+                config_reader_path = config_reader_path,
+                data_URL           = data_URL,
+                NOW                = NOW
+            )
+            print(f'{each_eaup_name}设备状态为{dhu_State}')
+            if dhu_State < 1:
+                print('设备处于非运行状态,跳过')
+                ALL_RESULT['STATUS']['Mode_Off'].append(each_eaup_name)
+                continue
+        except Exception as e:
+            ALL_RESULT['EXCEPTION']['State'][each_eaup_name] = e
+            raise e
+            continue
         
         # 加载模型
         try:
             MODEL = load_model(
                 each_eaup_name     = each_eaup_name,
+                each_equp_type     = each_equp_type,
                 config_reader      = config_reader,
                 config_reader_path = config_reader_path
             )
@@ -85,13 +106,11 @@ def optimize(*inputs,config=None):
             continue
         
         try:
-            DewOut_constrain = 'coil_3_DoutA-[coil_3_DoutA]<0'
             ALL_RESULT['STATUS']['Mode_Steady'].append(each_eaup_name)
             
             opt_result = optimize_dhu(
                 MODEL            = MODEL,
                 data_cur         = data_dhu,
-                DewOut_constrain = DewOut_constrain,
                 config_reader    = config_reader,
                 each_eaup_name   = each_eaup_name,
                 each_equp_type   = each_equp_type
@@ -99,6 +118,7 @@ def optimize(*inputs,config=None):
             ALL_OPT_RES.append(opt_result['opt_summary'][1].assign(equp_name=each_eaup_name))
         except Exception as e:
             ALL_RESULT['EXCEPTION']['Opt'][each_eaup_name] = e
+            raise e
             continue
             
         try:
@@ -121,6 +141,7 @@ def optimize(*inputs,config=None):
 
 def load_model(
     each_eaup_name,
+    each_equp_type,
     config_reader:ConfigReader,
     config_reader_path,
     use_adj_name = True
@@ -132,6 +153,13 @@ def load_model(
     else:
         pass
     
+    if each_equp_type in ['DHU_A','DHU_B']:
+        MODEL = DHU_AB
+    elif each_equp_type in ['SDHU_A','SDHU_B']:
+        MODEL = SDHU_AB
+    else:
+        raise NotImplementedError
+    
     if config_reader.get_app_info(
         each_eaup_name,
         app_type  = '实时优化',
@@ -139,9 +167,9 @@ def load_model(
         info_type = 'bool'
     ):
         # 从文件夹中获取临时模型
-        MODEL = DHU_AB.load(path=f'{config_reader_path}/model/{each_eaup_name}.pkl')
+        MODEL = MODEL.load(path=f'{config_reader_path}/model/{each_eaup_name}.pkl')
     else:
-        MODEL = DHU_AB.load_from_platform(
+        MODEL = MODEL.load_from_platform(
             source   = 'id',
             model_id = config_reader.get_equp_info(
                 equp_name = each_eaup_name,
@@ -151,6 +179,32 @@ def load_model(
         )
     return MODEL
 
+def load_dhu_State(
+    each_eaup_name,
+    config_reader,
+    config_reader_path,
+    data_URL,
+    NOW,
+):
+    data_last = (
+        DataLoader(
+            path          = f'{config_reader_path}/data/optimize/data_cur/',
+            start_time    = NOW - timedelta(minutes=1),
+            end_time      = NOW,
+            print_process = False
+        )
+        .download_equp_data(
+            equp_name   = each_eaup_name,
+            point       = {'State':config_reader.get_equp_point(each_eaup_name,equp_class=['A'])['State']},
+            url         = data_URL,
+            clean_cache = True
+        )
+        .get_equp_data(
+            equp_name = each_eaup_name,
+        )
+    )
+    return data_last.iat[-1,0]
+
 def load_data_room(
     each_eaup_name,
     each_equp_type,
@@ -221,12 +275,11 @@ def load_data_dhu(
     return data_cur
 
 def optimize_dhu(
-    MODEL,
-    data_cur,
-    DewOut_constrain,
-    config_reader,
-    each_eaup_name,
-    each_equp_type
+    MODEL           : Union[DHU_AB,SDHU_AB],
+    data_cur        : pd.DataFrame,
+    config_reader   : ConfigReader,
+    each_eaup_name  : str,
+    each_equp_type  : str
 ):
     # 模型精度判断
     predict  = MODEL.predict_system(input_data=data_cur)
@@ -242,23 +295,37 @@ def optimize_dhu(
     print(TVP_data)
     
     # 实时优化
-    constrains = [DewOut_constrain]
-    opt_res = MODEL.optimize(
-        cur_input_data=data_cur,
-        wheel_1_TinR=(
-            config_reader.get_equp_info(each_eaup_name,key='前再生温度上限',info_type='float'),
-            config_reader.get_equp_info(each_eaup_name,key='前再生温度下限',info_type='float')
-        ),
-        wheel_2_TinR=(
-            config_reader.get_equp_info(each_eaup_name,key='后再生温度上限',info_type='float'),
-            config_reader.get_equp_info(each_eaup_name,key='后再生温度下限',info_type='float')
-        ),
-        fan_2_Hz   = None,
-        constrains = constrains,
-        logging    = False,
-        target     = 'summary_waste',
-        target_min = True
-    )
+    
+    if each_equp_type in ['DHU_A','DHU_B']:
+        opt_res = MODEL.optimize(
+            cur_input_data=data_cur,
+            wheel_1_TinR=(
+                config_reader.get_equp_info(each_eaup_name,key='前再生温度上限',info_type='float'),
+                config_reader.get_equp_info(each_eaup_name,key='前再生温度下限',info_type='float')
+            ),
+            wheel_2_TinR=(
+                config_reader.get_equp_info(each_eaup_name,key='后再生温度上限',info_type='float'),
+                config_reader.get_equp_info(each_eaup_name,key='后再生温度下限',info_type='float')
+            ),
+            fan_2_Hz   = None,
+            constrains = ['coil_3_DoutA-[coil_3_DoutA]<0'],
+            logging    = False,
+            target     = 'summary_waste',
+            target_min = True
+        )
+    elif each_equp_type in ['SDHU_A','SDHU_B']:
+        opt_res = MODEL.optimize(
+            cur_input_data = data_cur,
+            wheel_1_TinR   = (
+                config_reader.get_equp_info(each_eaup_name,key='前再生温度上限',info_type='float'),
+                config_reader.get_equp_info(each_eaup_name,key='前再生温度下限',info_type='float')
+            ),
+            fan_2_Hz   = (30,45),
+            constrains = ['wheel_1_DoutP-[wheel_1_DoutP]<0'],
+            logging    = False,
+            target     = 'summary_waste',
+            target_min = True
+        )
     opt_summary = opt_res['opt_summary']
     opt_var     = opt_res['opt_var']
     print_dataframe(opt_summary,'优化结果')
@@ -304,6 +371,33 @@ def push_result(
                 ]
             },
         ]
+    elif each_equp_type in ['SDHU_A','SDHU_B']:
+        rcmd = [
+            {
+                'name'      : '再生加热温度',
+                'rcmd_value': opt_result['opt_var'][0].iat[0,0],
+                'children'  : [
+                    {
+                        'name'      : '再生加热温度',
+                        'rcmd_value': opt_result['opt_var'][0].round(1).iat[0,0],
+                        'curr_value': data_cur.loc[:,['wheel_1_TinR']].round(1).iat[0,0],
+                        'point_id'  : f'{each_eaup_name}_{equp_point["wheel_1_TinR_AISP"]}'
+                    }
+                ]
+            },
+            {
+                'name'      : '排风机频率',
+                'rcmd_value': opt_result['opt_var'][1].iat[0,0],
+                'children'  : [
+                    {
+                        'name'      : '排风机频率',
+                        'rcmd_value': opt_result['opt_var'][1].round(1).iat[0,0],
+                        'curr_value': data_cur.loc[:,['fan_2_Hz']].round(1).iat[0,0],
+                        'point_id'  : f'{each_eaup_name}_{equp_point["fan_2_Hz_AISP"]}'
+                    }
+                ]
+            },
+        ]
     else:
         raise Exception('MODEL_TYPE_ERROR')
     

+ 17 - 7
apps/DHU/train.py

@@ -172,12 +172,22 @@ def train_equp_model(each_eaup_name,each_equp_type,equp_data,config_reader,confi
     if not config_reader.get_app_info(each_eaup_name,'模型训练','训练设备模型','bool'):
         return None,None
     
-    equp_model.fit(
-        input_data    = equp_data_clean,
-        observed_data = equp_data_clean,
-        plot_TVP      = False,
-        rw_FA_val     = config_reader.get_app_info(each_eaup_name,'模型训练','新风阀门开度参数','bool')
-    )
+    if each_equp_type in ['DHU_A','DHU_B']:
+        equp_model.fit(
+            input_data    = equp_data_clean,
+            observed_data = equp_data_clean,
+            plot_TVP      = False,
+            rw_FA_val     = config_reader.get_app_info(each_eaup_name,'模型训练','新风阀门开度参数','bool')
+        )
+    elif each_equp_type in ['SDHU_A','SDHU_B']:
+        equp_model:SDHU_AB
+        equp_model.fit(
+            input_data    = equp_data_clean,
+            observed_data = equp_data_clean,
+            plot_TVP      = False
+        )
+    else:
+        raise NotImplementedError
     Path(f'{config_reader_path}/model').mkdir(parents=True, exist_ok=True)
     equp_model.save(f'{config_reader_path}/model/{each_eaup_name}.pkl')
     save_data(f'{config_reader_path}/data/train/data_TVP',f'{each_eaup_name}.csv',equp_model.TVP_data)
@@ -196,7 +206,7 @@ def train_room_model(each_eaup_name,each_equp_type,equp_data,config_reader:Confi
         Dout = equp_model.predict(equp_data.iloc[-N_fit:,:])['coil_3']['DoutA']
     elif each_equp_type in ['SDHU_A','SDHU_B']:
         equp_model = SDHU_AB.load(equp_model_path)
-        Dout = equp_model.predict(equp_data.iloc[-N_fit:,:])['wheel_1']['DoutA']
+        Dout = equp_model.predict(equp_data.iloc[-N_fit:,:])['wheel_1']['DoutP']
     else:
         raise NotImplementedError
     N_room = config_reader.get_equp_info(each_eaup_name,'房间数量','int')

+ 38 - 11
apps/Room/predict.py

@@ -5,18 +5,16 @@ from pprint import pprint
 
 import numpy as np
 import pandas as pd
-from workflowlib import requests
 
 from ..DHU.config_reader import ConfigReader
-from ..DHU.config_reader import ConfigReader as ConfigReader_DHU
 from ..._data.main import get_data
 from ..DHU.optimize import load_model
 from ...tools.data_loader import DataLoader
 from ..._utils.data_summary import print_dataframe,summary_dataframe
 from ...model.Room.room import RoomDewPredictor
+from ..DHU.optimize import load_dhu_State
 
 
-NOW             = datetime.now().replace(second=0,microsecond=0)
 PATH            = os.path.dirname(os.path.realpath(__file__)).replace('\\','/')
 MODEL_FUNC_PATH = f'{PATH}/model_func.py'
 MODEL_FILE_PATH = f'./model.pkl'
@@ -33,14 +31,32 @@ def predict(*inputs,config=None):
     
     for each_eaup_name in config_reader.all_equp_names:
         each_equp_type = config_reader.get_equp_info(each_eaup_name,key='设备类型',info_type='str')
+        
+        NOW = config_reader.get_app_info(each_eaup_name,'露点预测',key='开始时间',info_type='datetime')
+        
+        dhu_State = load_dhu_State(
+            each_eaup_name     = each_eaup_name,
+            config_reader      = config_reader,
+            config_reader_path = config_reader_path,
+            data_URL           = data_URL,
+            NOW                = NOW
+        )
+        print(f'{each_eaup_name}设备状态为{dhu_State}')
+        if dhu_State < 1:
+            print('设备处于非运行状态,跳过')
+            continue
+        
         equp_data = load_data(
+            cur_time           = NOW,
             each_eaup_name     = each_eaup_name,
             config_reader      = config_reader,
             config_reader_path = config_reader_path,
             data_URL           = data_URL,
         )
+        equp_data_sm = equp_data.rolling(10,min_periods=0).mean()
         equp_model = load_model(
             each_eaup_name     = each_eaup_name,
+            each_equp_type     = each_equp_type,
             config_reader      = config_reader,
             config_reader_path = config_reader_path,
             use_adj_name       = False
@@ -49,7 +65,13 @@ def predict(*inputs,config=None):
             equp_name  = each_eaup_name,
             equp_class = ['C','D']
         )
-        Dout_pred = equp_model.predict(equp_data)['coil_3']['DoutA'] #TODO 单转轮不一样
+        
+        if each_equp_type in ['DHU_A','DHU_B']:
+            Dout_pred = equp_model.predict(equp_data_sm)['coil_3']['DoutA']
+        elif each_equp_type in ['SDHU_A','SDHU_B']:
+            Dout_pred = equp_model.predict(equp_data_sm)['wheel_1']['DoutP']
+        else:
+            raise NotImplementedError
         
         for each_room_num in range(
             1,config_reader.get_equp_info(each_eaup_name,'房间数量','int') + 1
@@ -60,6 +82,9 @@ def predict(*inputs,config=None):
             Droom_cur  = equp_data.loc[:,f'room_{each_room_num}_Dpv'].values[-1]
             Droom_pred = room_model.predict_Droom(Dout=Dout_pred,Droom_cur=Droom_cur)
             pred_lag   = room_model.model_info['model_Droom']['lag']
+            print(f'{each_eaup_name}房间{each_room_num}在{NOW}的预测值')
+            print(f'完整预测时序:{Droom_pred}')
+            print(f'{pred_lag}分钟后的预测值为{Droom_pred[-1]}')
             index      = pd.Index(
                 pd.date_range(
                     start = NOW+timedelta(minutes=1),
@@ -67,16 +92,18 @@ def predict(*inputs,config=None):
                     freq  = '1min'
                 )
             )
+            # 完整时序预测
             write_ts_predict(
                 point_id = f"{each_eaup_name}_{room_SP_point[f'room_{each_room_num}_Dpv']}",
                 value    = Droom_pred,
                 ts       = index
             )
-            
+            # 最远预测点
             write_lag_predict(
                 point_id = f"{each_eaup_name}_{room_SP_point[f'room_{each_room_num}_Dpd']}",
                 value    = Droom_pred[-1]
             )
+            # 最远预测步长
             write_lag_predict(
                 point_id = f"{each_eaup_name}_{room_SP_point[f'room_{each_room_num}_Dpdlag']}",
                 value    = pred_lag
@@ -84,6 +111,7 @@ def predict(*inputs,config=None):
             
 
 def load_data(
+    cur_time,
     each_eaup_name,
     config_reader,
     config_reader_path,
@@ -92,8 +120,8 @@ def load_data(
     data = (
         DataLoader(
             path          = f'{config_reader_path}/data/room_predict/data_cur/',
-            start_time    = NOW - timedelta(minutes=120),
-            end_time      = NOW,
+            start_time    = cur_time - timedelta(minutes=120),
+            end_time      = cur_time,
             print_process = False
         )
         .download_equp_data(
@@ -109,11 +137,7 @@ def load_data(
     summary_dataframe(data,f'{each_eaup_name}除湿机数据')
     return data
 
-
-
-
 def write_ts_predict(point_id,value,ts):
-        
     ts_and_value = []
     for ts_i,value_i in zip(ts,value):
         each_ts_and_value = []
@@ -122,6 +146,7 @@ def write_ts_predict(point_id,value,ts):
         ts_and_value.append(each_ts_and_value)
     
     try:
+        from workflowlib import requests
         data = {'point_id': point_id, "json_data": ts_and_value}
         res = requests.post(
             'http://m2-backend-svc:8000/api/ai/predict_data/add', 
@@ -135,7 +160,9 @@ def write_ts_predict(point_id,value,ts):
         print(f'{point_id}写入预测时序失败,{e}')
 
 def write_lag_predict(point_id,value):
+    
     try:
+        from workflowlib import requests
         res   = requests.post(
             'http://basedataportal-svc:8080/value/set_value', 
             json={'point_id': point_id, "value": float(value)}

+ 3 - 0
main.py

@@ -9,6 +9,9 @@ def main(*args,config=None):
         elif mode == 'train':
             from .apps.DHU.train import train
             train(*args,config=config)
+        elif mode == 'monitor':
+            from .apps.DHU.monitor import monitor
+            monitor(*args,config=config)
         else:
             raise NotImplementedError(mode)
     elif equp_type == 'Room':

+ 2 - 2
model/DHU/DHU_AB.py

@@ -19,7 +19,7 @@ from ...tools.data_cleaner import DataCleaner
 
 class DHU_AB(BaseDevice):
     
-    val_rw_adj_target = ('coil_2_DoutA','coil_3_DoutA')
+    val_rw_adj_target = ('coil_2_DoutA','wheel_2_DoutP')
     
     def __init__(
         self,
@@ -868,7 +868,7 @@ def cal_Q_waste(
     heatingcoil_2_res,
     wheel_1_TinR,
     wheel_2_TinR
-):
+) -> dict:
     def waste_cond_func1(TinR):
         waste = 0.15 + 0.0001 * (TinR-70)**3
         return np.where(waste>0,waste,0)

+ 136 - 119
model/DHU/SDHU_AB.py

@@ -160,7 +160,11 @@ class SDHU_AB(BaseDevice):
         self.record_model(
             model_name   = 'ATD',
             model        = reorder_posterior(param_prior,self.param_posterior),
-            train_data   = {'x':np.array([1])},
+            train_data   = {
+                'wheel_1_TinR': observed_data.loc[:,'wheel_1_TinR'].values,
+                'fan_2_Hz'    : observed_data.loc[:,'wheel_2_TinR'].values,
+                'coil_2_DoutA': observed_data.loc[:,'coil_2_DoutA'].values,
+            },
             train_metric = {'R2':1,'MAE':1,'MAPE':1}
         )
         self.TVP_data   = self.get_TVP(self.param_posterior,observed_data)
@@ -204,99 +208,111 @@ class SDHU_AB(BaseDevice):
         return clean_data
 
     
-    # def optimize(
-    #     self,
-    #     cur_input_data: pd.DataFrame,
-    #     wheel_1_TinR  : tuple = (70,120),
-    #     wheel_2_TinR  : tuple = (70,120),
-    #     fan_2_Hz      : tuple = (30,50),
-    #     constrains    : list  = None,
-    #     logging       : bool  = True,
-    #     target        : str   = 'summary_Fs',
-    #     target_min    : bool  = True
-    # ) -> list:
-    #     constrains = [] if constrains is None else constrains
-    #     cur_input_data = cur_input_data.iloc[[0],:]
+    def optimize(
+        self,
+        cur_input_data: pd.DataFrame,
+        wheel_1_TinR  : tuple = (70,120),
+        fan_2_Hz      : tuple = (30,50),
+        constrains    : list  = None,
+        logging       : bool  = True,
+        target        : str   = 'summary_Fs',
+        target_min    : bool  = True
+    ) -> list:
+        constrains = [] if constrains is None else constrains
+        cur_input_data = cur_input_data.iloc[[0],:]
         
-    #     opt_var_boundary = {}
-    #     if wheel_1_TinR is not None:
-    #         opt_var_boundary['wheel_1_TinR'] = {'lb':min(wheel_1_TinR),'ub':max(wheel_1_TinR)}
-    #     if wheel_2_TinR is not None:
-    #         opt_var_boundary['wheel_2_TinR'] = {'lb':min(wheel_2_TinR),'ub':max(wheel_2_TinR)}
-    #     if fan_2_Hz is not None:
-    #         opt_var_boundary['fan_2_Hz'] = {'lb':min(fan_2_Hz),'ub':max(fan_2_Hz)}
+        opt_var_boundary = {}
+        if wheel_1_TinR is not None:
+            opt_var_boundary['wheel_1_TinR'] = {'lb':min(wheel_1_TinR),'ub':max(wheel_1_TinR)}
+        if fan_2_Hz is not None:
+            opt_var_boundary['fan_2_Hz'] = {'lb':min(fan_2_Hz),'ub':max(fan_2_Hz)}
             
-    #     opt_var_value = cur_input_data.loc[:,list(opt_var_boundary.keys())]
-    #     oth_var_value = (
-    #         cur_input_data
-    #         .loc[:,list(self.model_input_data_columns.values())]
-    #         .drop(opt_var_value.columns,axis=1)
-    #     )
-    #     opt_res = optimizer(
-    #         model            = self,
-    #         opt_var_boundary = opt_var_boundary,
-    #         opt_var_value    = opt_var_value,
-    #         oth_var_value    = oth_var_value,
-    #         target           = target,
-    #         target_min       = target_min,
-    #         constrains       = constrains,
-    #         logging          = logging,
-    #         other_kwargs     = {'NIND':2000,'MAXGEN':50}
-    #     )
-    #     return opt_res
+        opt_var_value = cur_input_data.loc[:,list(opt_var_boundary.keys())]
+        oth_var_value = (
+            cur_input_data
+            .loc[:,list(set(self.model_input_data_columns.values()))]
+            .drop(opt_var_value.columns,axis=1)
+        )
+        opt_res = optimizer(
+            model            = self,
+            opt_var_boundary = opt_var_boundary,
+            opt_var_value    = opt_var_value,
+            oth_var_value    = oth_var_value,
+            target           = target,
+            target_min       = target_min,
+            constrains       = constrains,
+            logging          = logging,
+            other_kwargs     = {'NIND':2000,'MAXGEN':50}
+        )
+        return opt_res
     
     
-    # def plot_opt(
-    #     self,
-    #     cur_input_data: pd.DataFrame,
-    #     target_min    : str   = 'summary_waste',
-    # ):
-    #     data_input = (
-    #         pd.MultiIndex.from_product(
-    #             [
-    #                 np.linspace(70,120,1000),
-    #                 np.linspace(70,120,1000),
-    #             ],
-    #             names=['wheel_1_TinR','wheel_2_TinR']
-    #         )
-    #         .to_frame(index=False)
-    #     )
-    #     for col in cur_input_data.columns:
-    #         if col in data_input.columns:
-    #             continue
-    #         data_input[col] = cur_input_data.loc[:,col].iat[0]
-    #     data_output = self.predict_system(data_input)
-    #     data = (
-    #         data_output
-    #         .assign(
-    #             wheel_1_TinR = data_input.loc[:,'wheel_1_TinR'],
-    #             wheel_2_TinR = data_input.loc[:,'wheel_2_TinR'],
-    #         )
-    #         .assign(coil_3_DoutA=lambda dt:dt.coil_3_DoutA.round(1))
-    #         .loc[lambda dt:dt.groupby('coil_3_DoutA')[target_min].idxmin()]
-    #         .loc[lambda dt:dt.coil_3_DoutA.mod(1)==0]
-    #     )
-    #     import plotnine as gg
-    #     plot = (
-    #         data
-    #         .pipe(gg.ggplot)
-    #         + gg.aes(x='wheel_1_TinR',y='wheel_2_TinR')
-    #         + gg.geom_path(size=1)
-    #         + gg.geom_point()
-    #         + gg.geom_label(gg.aes(label='coil_3_DoutA'))
-    #         + gg.geom_abline(slope=1,intercept=0,color='red',linetype='--')
-    #     )
-    #     return plot
+    def plot_opt(
+        self,
+        cur_input_data: pd.DataFrame,
+        target_min    : str   = 'summary_waste',
+        coil_2_DoutA  : tuple = None
+    ):
+        if coil_2_DoutA is None:
+            coil_2_DoutA = (
+                self.model_info['model_train_info_ATD']['coil_2_DoutA_min'],
+                self.model_info['model_train_info_ATD']['coil_2_DoutA_max']
+            )
+        data_input = (
+            pd.MultiIndex.from_product(
+                [
+                    np.linspace(
+                        self.model_info['model_train_info_ATD']['wheel_1_TinR_min']-5,
+                        self.model_info['model_train_info_ATD']['wheel_1_TinR_max']+5,
+                        1000
+                    ),
+                    np.linspace(
+                        self.model_info['model_train_info_ATD']['fan_2_Hz_min']-5,
+                        self.model_info['model_train_info_ATD']['fan_2_Hz_max']+5,
+                        1000
+                    ),
+                ],
+                names=['wheel_1_TinR','fan_2_Hz']
+            )
+            .to_frame(index=False)
+        )
+        for col in cur_input_data.columns:
+            if col in data_input.columns:
+                continue
+            data_input[col] = cur_input_data.loc[:,col].iat[0]
+        data_output = self.predict_system(data_input)
+        data = (
+            data_output
+            .assign(
+                wheel_1_TinR = data_input.loc[:,'wheel_1_TinR'],
+                fan_2_Hz     = data_input.loc[:,'fan_2_Hz'],
+            )
+            .assign(coil_2_DoutA=lambda dt:dt.coil_2_DoutA.round(1))
+            .loc[lambda dt:dt.coil_2_DoutA.between(*(min(coil_2_DoutA),max(coil_2_DoutA)))]
+            .loc[lambda dt:dt.groupby('coil_2_DoutA')[target_min].idxmin()]
+            .loc[lambda dt:dt.coil_2_DoutA.mod(0.5)==0]
+        )
+        import plotnine as gg
+        plot = (
+            data
+            .pipe(gg.ggplot)
+            + gg.aes(x='wheel_1_TinR',y='fan_2_Hz')
+            + gg.geom_path(size=1)
+            + gg.geom_point()
+            + gg.geom_label(gg.aes(label='coil_2_DoutA'))
+            + gg.geom_abline(slope=1,intercept=0,color='red',linetype='--')
+        )
+        return plot
     
-    # def plot_check(self,cur_input_data:pd.DataFrame) -> dict:
-    #     pa1=self.curve(input_data=cur_input_data,x='wheel_1_TinR',y='wheel_1_DoutP')
-    #     pa2=self.curve(input_data=cur_input_data,x='wheel_1_TinR',y='wheel_1_ToutP')
-    #     pa3=self.curve(input_data=cur_input_data,x='wheel_1_TinR',y='wheel_1_EFF')
-    #     pb1=self.curve(input_data=cur_input_data,x='wheel_2_TinR',y='wheel_2_DoutP')
-    #     pb2=self.curve(input_data=cur_input_data,x='wheel_2_TinR',y='wheel_2_ToutP')
-    #     pb3=self.curve(input_data=cur_input_data,x='wheel_2_TinR',y='wheel_2_EFF')
-    #     plot1 = (pa1|pa2|pa3)/(pb1|pb2|pb3)
-    #     return {'plot1':plot1}
+    def plot_check(self,cur_input_data:pd.DataFrame) -> dict:
+        pa1=self.curve(input_data=cur_input_data,x='wheel_1_TinR',y='wheel_1_DoutP')
+        pa2=self.curve(input_data=cur_input_data,x='wheel_1_TinR',y='wheel_1_ToutP')
+        pa3=self.curve(input_data=cur_input_data,x='wheel_1_TinR',y='wheel_1_EFF')
+        pb1=self.curve(input_data=cur_input_data,x='fan_2_Hz',y='wheel_1_DoutP')
+        pb2=self.curve(input_data=cur_input_data,x='fan_2_Hz',y='wheel_1_ToutP')
+        pb3=self.curve(input_data=cur_input_data,x='fan_2_Hz',y='wheel_1_EFF')
+        plot1 = (pa1|pa2|pa3)/(pb1|pb2|pb3)
+        return {'plot1':plot1}
         
         
         
@@ -384,15 +400,12 @@ def model_A(
         engine = engine
     )
     
-    
-    # waste = cal_Q_waste(
-    #     wheel_1_res       = wheel_1_res_adj,
-    #     wheel_2_res       = wheel_2_res_adj,
-    #     heatingcoil_1_res = heatingcoil_1_res,
-    #     heatingcoil_2_res = heatingcoil_2_res,
-    #     wheel_1_TinR      = wheel_1_TinR,
-    #     wheel_2_TinR      = wheel_2_TinR
-    # )
+    waste = cal_Q_waste(
+        wheel_1_res       = wheel_1_res_adj,
+        heatingcoil_1_res = heatingcoil_1_res,
+        wheel_1_TinR      = wheel_1_TinR,
+        fan_2_Hz          = fan_2_Hz
+    )
     return {
         'coil_2'       : coil_2_res,
         'wheel_1'      : wheel_1_res_adj,
@@ -401,7 +414,7 @@ def model_A(
         'Fa'           : air_flow,
         'summary'      : {
             'Fs'  : heatingcoil_1_res['Fs'],
-            # **waste,
+            **waste,
         }
     }
         
@@ -442,31 +455,35 @@ class AirFlow_SDHU_A:
         return param
         
         
-    
+
 def cal_Q_waste(
     wheel_1_res,
-    wheel_2_res,
     heatingcoil_1_res,
-    heatingcoil_2_res,
     wheel_1_TinR,
-    wheel_2_TinR
-):
+    fan_2_Hz
+) -> dict:
+    def waste_cond_func1(TinR):
+        waste = 0.15 + 0.0001 * (TinR-70)**3
+        return np.where(waste>0,waste,0)
+    def waste_cond_func2(TinR):
+        waste = 0.25 * (1 - np.exp(-0.04 * (TinR - 70)))
+        return np.where(waste>0,waste,0)
+    
+    heatingcoil_1_Q = heatingcoil_1_res['Q']
+    heatingcoil_1_Q = np.where(heatingcoil_1_Q>0,heatingcoil_1_Q,0)
+    
     waste_Qsen1 = wheel_1_res['Qsen']
-    waste_Qsen2 = wheel_2_res['Qsen']
-    waste_cond1 = heatingcoil_1_res['Q'] * (0.15 + 0.0001 * (wheel_1_TinR-70)**2)
-    waste_cond2 = heatingcoil_2_res['Q'] * (0.15 + 0.0001 * (wheel_2_TinR-70)**2)
-    waste_out = (
-        heatingcoil_1_res['Q'] + heatingcoil_2_res['Q']
-        - wheel_1_res['Qsen'] - wheel_1_res['Qlat']
-        - wheel_2_res['Qsen'] - wheel_2_res['Qlat']
-    )
-    return {
+    waste_cond1 = heatingcoil_1_Q * waste_cond_func1(wheel_1_TinR)
+    
+    res = {
         'waste_Qsen1': waste_Qsen1,
-        'waste_Qsen2': waste_Qsen2,
-        'waste_Qout' : waste_out,
         'waste_cond1': waste_cond1,
-        'waste_cond2': waste_cond2,
-        'waste_out'  : waste_out,
-        'waste'      : waste_Qsen1+waste_cond2+waste_cond1+waste_cond2+waste_out,
     }
-
+    
+    waste_out = heatingcoil_1_Q - wheel_1_res['Qsen'] - wheel_1_res['Qlat']
+    waste_out = np.where(waste_out>0,waste_out,0)
+    waste = waste_Qsen1 + waste_cond1 + waste_out + fan_2_Hz/100
+    res['waste_out']   = waste_out
+    res['waste'] = waste
+    
+    return res

+ 31 - 13
model/Room/room.py

@@ -24,15 +24,21 @@ class RoomDewPredictor(BaseModel):
         )
         return self
     
-    def predict_Droom(self,Dout:np.ndarray,Droom_cur:float) -> np.ndarray:
-        model    = self.model_info['model_Droom']
-        lag      = model['lag']
-        coef     = model['coef']
-        diffdata = self.to_diffdata(Dout=Dout,Droom=None)
-        Dout_lag = (diffdata.loc[:,f'Dout_lag{lag:02d}'].values * coef).cumsum()[-lag:]
-        Dout_lag = Dout_lag - Dout_lag[0]
-        pred = Droom_cur + Dout_lag
-        return pred
+    @property
+    def lag(self):
+        return self.model_info['model_Droom']['lag']
+    
+    def predict_Droom(self,Dout:np.ndarray,Droom_cur:float,sm_frac=0) -> np.ndarray:
+        if isinstance(Droom_cur,np.ndarray):
+            Droom_cur = Droom_cur[-1]
+        model      = self.model_info['model_Droom']
+        lag        = model['lag']
+        coef       = model['coef']
+        Dout       = Dout[-lag-1:]
+        Dout_diff  = np.diff(Dout)
+        Droom_diff = Dout_diff * coef
+        Droom      = smooth(Droom_diff.cumsum() + Droom_cur,sm_frac)
+        return Droom
     
     @classmethod
     def to_diffdata(
@@ -61,16 +67,23 @@ class RoomDewPredictor(BaseModel):
         Dout : np.ndarray,
         Droom: np.ndarray
     ):
-        data = self.to_diffdata(Dout=Dout,Droom=Droom)
-        plot = (
-            data
+        plot_raw = (
+            pd.DataFrame({'Dout':Dout,'Droom':Droom})
+            .assign(ts=np.arange(len(Dout)))
+            .pipe(gg.ggplot)
+            + gg.aes(x='ts')
+            + gg.geom_line(gg.aes(y='Droom'),color='red')
+            + gg.geom_line(gg.aes(y='Dout'),color='blue')
+        )
+        plot_diff = (
+            self.to_diffdata(Dout=Dout,Droom=Droom)
             .reset_index()
             .pipe(gg.ggplot)
             + gg.aes(x='ts')
             + gg.geom_line(gg.aes(y='Droom'),color='red')
             + gg.geom_line(gg.aes(y='Dout'),color='blue')
-            + gg.theme(figure_size=[8,4])
         )
+        plot = (plot_raw / plot_diff) + gg.theme(figure_size=[8,6])
         return plot
     
     def plot_diffdata_lagcorr(self,Dout:np.ndarray, Droom:np.ndarray):
@@ -136,3 +149,8 @@ class RoomDewPredictor(BaseModel):
         best_coef = all_coef[all_r2.argmax()]
         return all_coef,all_r2,best_lag,best_coef
     
+def smooth(y:pd.Series,frac=0.1):
+    import statsmodels.api as sm
+    x             = np.arange(len(y))
+    lowess_result = sm.nonparametric.lowess(y, x, frac=frac)  # frac 控制平滑程度
+    return lowess_result[:, 1]

+ 3 - 0
model/_base/_base_device.py

@@ -93,6 +93,9 @@ class BaseDevice(BaseModel):
         
         return plot
     
+    def plot_check(self,cur_input_data:pd.DataFrame) -> dict:
+        return {}
+    
     
     def curve(
         self,

+ 10 - 10
tools/data_loader.py

@@ -19,8 +19,8 @@ from .._data.main import get_data
 class DataLoader:
     def __init__(self,path,start_time,end_time,print_process=True):
         self.path          = path
-        self.start_time    = start_time
-        self.end_time      = end_time
+        self.start_time    = start_time.replace(second=0,microsecond=0)
+        self.end_time      = end_time.replace(second=0,microsecond=0)
         self.int_time      = 'min'
         self.date_range    = pd.date_range(start=self.start_time,end=self.end_time,freq=self.int_time)
         self.print_process = print_process
@@ -125,23 +125,23 @@ class DataLoader:
             exist_R = point_name.replace('_T','_R') in all_download_file
             exist_D = point_name.replace('_T','_D') in all_download_file
             if exist_T and exist_R and not exist_D:
-                Tdb = pd.read_pickle(os.path.join(equp_path,file)).loc[slice_time].iloc[:,0].values
-                RH  = pd.read_pickle(os.path.join(equp_path,file.replace('_T','_R'))).loc[slice_time].iloc[:,0].values
-                Dew = pd.DataFrame({point_name.replace('_T','_D'):get_Dew(Tdb,np.clip(RH,0,100)/100)},index=self.date_range)
+                Tdb = pd.read_pickle(os.path.join(equp_path,file)).loc[slice_time].iloc[:,0]
+                RH  = pd.read_pickle(os.path.join(equp_path,file.replace('_T','_R'))).loc[slice_time].iloc[:,0]
+                Dew = pd.DataFrame({point_name.replace('_T','_D'):get_Dew(Tdb,np.clip(RH,0,100)/100)},index=Tdb.index)
                 pd.to_pickle(Dew,os.path.join(equp_path,file.replace('_T','_D')))
             if exist_T and exist_D and not exist_R:
-                Tdb = pd.read_pickle(os.path.join(equp_path,file)).loc[slice_time].iloc[:,0].values
-                Dew = pd.read_pickle(os.path.join(equp_path,file.replace('_T','_D'))).loc[slice_time].iloc[:,0].values
+                Tdb = pd.read_pickle(os.path.join(equp_path,file)).loc[slice_time].iloc[:,0]
+                Dew = pd.read_pickle(os.path.join(equp_path,file.replace('_T','_D'))).loc[slice_time].iloc[:,0]
                 Dew = np.where(Dew>Tdb,Tdb,Dew)
-                RH  = pd.DataFrame({point_name.replace('_T','_R'):get_RH(Tdb,Dew)},index=self.date_range)
+                RH  = pd.DataFrame({point_name.replace('_T','_R'):get_RH(Tdb,Dew)},index=Tdb.index)
                 pd.to_pickle(RH,os.path.join(equp_path,file.replace('_T','_R')))
                 
         all_file_path = os.listdir(equp_path)
         for file in all_file_path:
             # 通过露点计算绝对湿度
             if '_D' in file:
-                Dew = pd.read_pickle(os.path.join(equp_path,file)).loc[slice_time].iloc[:,0].values
-                Hr  = pd.DataFrame({file.replace('_D','_H'):get_Hr(Dew,101325)},index=self.date_range)
+                Dew = pd.read_pickle(os.path.join(equp_path,file)).loc[slice_time].iloc[:,0]
+                Hr  = pd.DataFrame({file.replace('_D','_H'):get_Hr(Dew,101325)},index=Dew.index)
                 pd.to_pickle(Hr,os.path.join(equp_path,file.replace('_D','_H')))
         return self