health_evalution_class.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. import numpy as np
  2. import pandas as pd
  3. from sklearn.neighbors import BallTree
  4. from typing import Dict, List, Optional, Union
  5. from functools import lru_cache
  6. class HealthAssessor:
  7. def __init__(self):
  8. "八个子系统"
  9. self.subsystem_config = {
  10. # 偏航系统
  11. 'YawSystem': {
  12. # 双馈
  13. 'dfig': {
  14. 'fixed': ['yaw_ang_1','yaw_ang_2','twist_ang_1','twist_ang_2','yaw_err_1','yaw_err_2','wind_dir_1','wind_dir_2','wind_dir_3','yaw_to_wind_ang_1',
  15. 'yaw_to_wind_ang_2','yaw_to_wind_ang_3','total_yaw_cnt','untwist_cnt_1','untwist_cnt_2','yaw_sts_1','yaw_sts_2','cw_yaw_sts_1','ccw_yaw_sts_1',
  16. 'cw_yaw_sts_2','ccw_yaw_sts_2','yaw_sys_prs','yaw_brk_prs','yaw_motor_brk','yaw_hyd_brk','yaw_brk_valve_sts'],
  17. },
  18. # 直驱
  19. 'direct': {
  20. 'fixed': ['yaw_ang_1','yaw_ang_2','twist_ang_1','twist_ang_2','yaw_err_1','yaw_err_2','wind_dir_1','wind_dir_2','wind_dir_3','yaw_to_wind_ang_1',
  21. 'yaw_to_wind_ang_2','yaw_to_wind_ang_3','total_yaw_cnt','untwist_cnt_1','untwist_cnt_2','yaw_sts_1','yaw_sts_2','cw_yaw_sts_1','ccw_yaw_sts_1',
  22. 'cw_yaw_sts_2','ccw_yaw_sts_2','yaw_sys_prs','yaw_brk_prs','yaw_motor_brk','yaw_hyd_brk','yaw_brk_valve_sts'],
  23. }
  24. },
  25. # 变桨系统
  26. 'PicthSystem': {
  27. # 双馈
  28. 'dfig': {
  29. 'fixed': ['pitch_ang_set_1','pitch_ang_set_2','pitch_ang_set_3','pitch_ang_set_4','pitch_ang_set_5','pitch_ang_set_6','pitch_ang_act_1','pitch_ang_act_2','pitch_ang_act_3','pitch_ang_act_4','pitch_ang_act_5','pitch_ang_act_16',
  30. 'pitch_ang_act_7','pitch_ang_act_7','pitch_ang_act_9','pitch_spd_1','pitch_spd_2','pitch_spd_3','pitch_spd_4','pitch_spd_5','pitch_spd_6','pitch_motor_pwr_1','pitch_motor_pwr_2',
  31. 'pitch_motor_pwr_3','pitch_motor_cur_1','pitch_motor_cur_2','pitch_motor_cur_3','pitch_motor_cur_4','pitch_motor_cur_5','pitch_motor_cur_6',
  32. 'pitch_motor_temp_1','pitch_motor_temp_2','pitch_motor_temp_3','pitch_motor_temp_4','pitch_motor_temp_5','pitch_motor_temp_6','pitch_cab_temp_1',
  33. 'pitch_cab_temp_2','pitch_cab_temp_3','pitch_cab_temp_4','pitch_cab_temp_5','pitch_cab_temp_6','pitch_bat_temp_1','pitch_bat_temp_2','pitch_bat_temp_3',
  34. 'pitch_bat_temp_4','pitch_bat_temp_5','pitch_bat_temp_6'],
  35. },
  36. # 直驱
  37. 'direct': {
  38. 'fixed': ['pitch_ang_set_1','pitch_ang_set_2','pitch_ang_set_3','pitch_ang_set_4','pitch_ang_set_5','pitch_ang_set_6','pitch_ang_act_1','pitch_ang_act_2','pitch_ang_act_3','pitch_ang_act_4','pitch_ang_act_5','pitch_ang_act_16',
  39. 'pitch_ang_act_7','pitch_ang_act_7','pitch_ang_act_9','pitch_spd_1','pitch_spd_2','pitch_spd_3','pitch_spd_4','pitch_spd_5','pitch_spd_6','pitch_motor_pwr_1','pitch_motor_pwr_2',
  40. 'pitch_motor_pwr_3','pitch_motor_cur_1','pitch_motor_cur_2','pitch_motor_cur_3','pitch_motor_cur_4','pitch_motor_cur_5','pitch_motor_cur_6',
  41. 'pitch_motor_temp_1','pitch_motor_temp_2','pitch_motor_temp_3','pitch_motor_temp_4','pitch_motor_temp_5','pitch_motor_temp_6','pitch_cab_temp_1',
  42. 'pitch_cab_temp_2','pitch_cab_temp_3','pitch_cab_temp_4','pitch_cab_temp_5','pitch_cab_temp_6','pitch_bat_temp_1','pitch_bat_temp_2','pitch_bat_temp_3',
  43. 'pitch_bat_temp_4','pitch_bat_temp_5','pitch_bat_temp_6'],
  44. }
  45. },
  46. # 主轴
  47. 'MainShaft': {
  48. # 双馈
  49. 'dfig': {
  50. 'fixed': ['main_shaft_spd_1','main_shaft_spd_2','main_shaft_spd_3','main_brg_temp_1','main_brg_temp_2','main_brg_temp_3','main_brg_temp_4','lube_oil_temp',
  51. 'gen_lube_level_sts','gen_lube_pulse_sts','gen_lube_pump_act','gen_brg_lube_level_sig','oil_prs'],
  52. },
  53. # 直驱
  54. 'direct': {
  55. 'fixed': ['main_shaft_spd_1','main_shaft_spd_2','main_shaft_spd_3','main_brg_temp_1','main_brg_temp_2','main_brg_temp_3','main_brg_temp_4','lube_oil_temp',
  56. 'gen_lube_level_sts','gen_lube_pulse_sts','gen_lube_pump_act','gen_brg_lube_level_sig','oil_prs'],
  57. }
  58. },
  59. # 齿轮箱
  60. 'Gearbox': {
  61. # 双馈
  62. 'dfig': {
  63. 'fixed': ['gearbox_spd_1','gearbox_spd_2','gearbox_spd_3','hss_brg_temp_1','hss_brg_temp_2','hss_brg_temp_3','hss_brg_temp_4','hss_brg_temp_5','iss_brg_temp_1',
  64. 'iss_brg_temp_2','iss_brg_temp_3','iss_brg_temp_4','lss_brg_temp_1','lss_brg_temp_2','lss_brg_temp_3','lss_brg_temp_4','gearbox_oil_temp_1','gearbox_oil_temp_2',
  65. 'gearbox_oil_temp_3','gb_out_oil_prs_1','gb_out_oil_prs_2','gb_out_oil_prs_3','gb_in_oil_prs_1','gb_in_oil_prs_2','gb_cool_water_temp'],
  66. },
  67. # 直驱
  68. 'direct': {
  69. 'fixed': ['gearbox_spd_1','gearbox_spd_2','gearbox_spd_3','hss_brg_temp_1','hss_brg_temp_2','hss_brg_temp_3','hss_brg_temp_4','hss_brg_temp_5','iss_brg_temp_1',
  70. 'iss_brg_temp_2','iss_brg_temp_3','iss_brg_temp_4','lss_brg_temp_1','lss_brg_temp_2','lss_brg_temp_3','lss_brg_temp_4','gearbox_oil_temp_1','gearbox_oil_temp_2',
  71. 'gearbox_oil_temp_3','gb_out_oil_prs_1','gb_out_oil_prs_2','gb_out_oil_prs_3','gb_in_oil_prs_1','gb_in_oil_prs_2','gb_cool_water_temp'],
  72. }
  73. },
  74. # 发电机
  75. 'Generator': {
  76. # 双馈
  77. 'dfig': {
  78. 'fixed': ['gen_spd_1','gen_spd_2','gen_spd_3','gen_spd_4','gen_de_brg_temp_1','gen_nde_brg_temp_1','gen_de_brg_temp_2','gen_nde_brg_temp_2','gen_de_brg_temp_3','gen_nde_brg_temp_3',
  79. 'rotor_brk_prs','rotor_temp_1','rotor_temp_2','rotor_temp_3','stator_cur','stator_temp','stator_wind_temp_1','stator_wind_temp_2','stator_wind_temp_3','stator_wind_temp_4',
  80. 'stator_wind_temp_5','stator_wind_temp_6','gen_in_water_temp','gen_out_water_temp_1','gen_out_water_temp_2','gen_in_air_temp_1','gen_in_air_temp_2','gen_out_air_temp_1','gen_out_air_temp_2'],
  81. },
  82. # 直驱
  83. 'direct': {
  84. 'fixed': ['gen_spd_1','gen_spd_2','gen_spd_3','gen_spd_4','gen_de_brg_temp_1','gen_nde_brg_temp_1','gen_de_brg_temp_2','gen_nde_brg_temp_2','gen_de_brg_temp_3','gen_nde_brg_temp_3',
  85. 'rotor_brk_prs','rotor_temp_1','rotor_temp_2','rotor_temp_3','stator_cur','stator_temp','stator_wind_temp_1','stator_wind_temp_2','stator_wind_temp_3','stator_wind_temp_4',
  86. 'stator_wind_temp_5','stator_wind_temp_6','gen_in_water_temp','gen_out_water_temp_1','gen_out_water_temp_2','gen_in_air_temp_1','gen_in_air_temp_2','gen_out_air_temp_1','gen_out_air_temp_2'],
  87. }
  88. },
  89. # 变流器
  90. 'Converter': {
  91. # 双馈
  92. 'dfig': {
  93. 'fixed': ['grid_ia_1','grid_ib_1','grid_ic_1','grid_ia_2','grid_ib_2','grid_ic_2','grid_ua_1','grid_ub_1','grid_uc_1','grid_ua_2','grid_ub_2','grid_uc_2','phase_ang_a_1','phase_ang_b_1','phase_ang_c_1',
  94. 'phase_ang_a_2','phase_ang_b_2','phase_ang_c_2','conv_grid_freq_1','conv_grid_freq_2','conv_grid_freq_3','conv_spd_1','conv_spd_2','conv_mc_temp_1','conv_mc_temp_2','conv_mc_temp_3','conv_mc_temp_4',
  95. 'conv_mc_temp_5','conv_mc_temp_6','conv_mc_temp_7','conv_mc_temp_8','conv_mc_temp_9','conv_mc_temp_10','conv_gc_temp_1','conv_gc_temp_2','conv_gc_temp_3','conv_gc_temp_4','conv_gc_temp_5','conv_gc_temp_6',
  96. 'conv_gc_temp_7','conv_gc_temp_8','conv_gc_temp_9','conv_gc_temp_10','conv_fault_1','conv_fault_2','conv_fault_3','conv_fault_4','conv_err_1','conv_err_2','conv_alarm_1','conv_alarm_2','conv_cool_in_temp_1',
  97. 'conv_cool_in_temp_2','conv_cool_out_temp_1','conv_cool_out_temp_2','conv_in_air_temp','conv_out_air_temp' ],
  98. },
  99. # 直驱
  100. 'direct': {
  101. 'fixed': ['grid_ia_1','grid_ib_1','grid_ic_1','grid_ia_2','grid_ib_2','grid_ic_2','grid_ua_1','grid_ub_1','grid_uc_1','grid_ua_2','grid_ub_2','grid_uc_2','phase_ang_a_1','phase_ang_b_1','phase_ang_c_1',
  102. 'phase_ang_a_2','phase_ang_b_2','phase_ang_c_2','conv_grid_freq_1','conv_grid_freq_2','conv_grid_freq_3','conv_spd_1','conv_spd_2','conv_mc_temp_1','conv_mc_temp_2','conv_mc_temp_3','conv_mc_temp_4',
  103. 'conv_mc_temp_5','conv_mc_temp_6','conv_mc_temp_7','conv_mc_temp_8','conv_mc_temp_9','conv_mc_temp_10','conv_gc_temp_1','conv_gc_temp_2','conv_gc_temp_3','conv_gc_temp_4','conv_gc_temp_5','conv_gc_temp_6',
  104. 'conv_gc_temp_7','conv_gc_temp_8','conv_gc_temp_9','conv_gc_temp_10','conv_fault_1','conv_fault_2','conv_fault_3','conv_fault_4','conv_err_1','conv_err_2','conv_alarm_1','conv_alarm_2','conv_cool_in_temp_1',
  105. 'conv_cool_in_temp_2','conv_cool_out_temp_1','conv_cool_out_temp_2','conv_in_air_temp','conv_out_air_temp' ],
  106. }
  107. },
  108. # 液压系统
  109. 'HPU': {
  110. # 双馈
  111. 'dfig': {
  112. 'fixed': ['hyd_sys_prs_1','hyd_sys_prs_2','hyd_sys_prs_3','hyd_pump_prs','hyd_pump_start_cnt','oil_pump_motor','hyd_tank_level','hyd_oil_temp','hyd_oil_prs','hyd_level_sts_1','hyd_level_sts_2','hyd_oil_temp_sts'],
  113. },
  114. # 直驱
  115. 'direct': {
  116. 'fixed': ['hyd_sys_prs_1','hyd_sys_prs_2','hyd_sys_prs_3','hyd_pump_prs','hyd_pump_start_cnt','oil_pump_motor','hyd_tank_level','hyd_oil_temp','hyd_oil_prs','hyd_level_sts_1','hyd_level_sts_2','hyd_oil_temp_sts'],
  117. }
  118. },
  119. # 主控系统
  120. 'MCS': {
  121. # 双馈
  122. 'dfig': {
  123. 'fixed': ['wtg_sts_1','wtg_sts_2','wtg_sts_3','wtg_sts_4','wind_spd_1','wind_spd_2','wind_spd_3','wind_spd_4','p_active_1','p_active_2','p_active_3','p_active_4','p_reactive_1','p_reactive_2','p_reactive_3','rotor_spd_1',
  124. 'rotor_spd_2','rotor_spd_3','safety_chain_1','safety_chain_2','safety_chain_3','nacelle_estop_1','nacelle_estop_2','nacelle_estop_3','tower_estop_1','tower_estop_2','tower_estop_3','hand_estop','nacelle_in_temp_1',
  125. 'nacelle_in_temp_2','nacelle_out_temp_1','nacelle_out_temp_2','tower_fa_vib_1','tower_ss_vib_1','tower_fa_vib_2','tower_ss_vib_2','tower_fa_vib_3','tower_ss_vib_3','tower_env_temp_1','tower_env_temp_2','nacelle_cab_temp_1',
  126. 'nacelle_cab_temp_2','tower_cab_temp_1','tower_cab_temp_2','nacelle_ups_1','nacelle_ups_2','nacelle_ups_3','tower_ups_1','tower_ups_2','tower_ups_3','tower_ups_4' ],
  127. },
  128. # 直驱
  129. 'direct': {
  130. 'fixed': ['wtg_sts_1','wtg_sts_2','wtg_sts_3','wtg_sts_4','wind_spd_1','wind_spd_2','wind_spd_3','wind_spd_4','p_active_1','p_active_2','p_active_3','p_active_4','p_reactive_1','p_reactive_2','p_reactive_3','rotor_spd_1',
  131. 'rotor_spd_2','rotor_spd_3','safety_chain_1','safety_chain_2','safety_chain_3','nacelle_estop_1','nacelle_estop_2','nacelle_estop_3','tower_estop_1','tower_estop_2','tower_estop_3','hand_estop','nacelle_in_temp_1',
  132. 'nacelle_in_temp_2','nacelle_out_temp_1','nacelle_out_temp_2','tower_fa_vib_1','tower_ss_vib_1','tower_fa_vib_2','tower_ss_vib_2','tower_fa_vib_3','tower_ss_vib_3','tower_env_temp_1','tower_env_temp_2','nacelle_cab_temp_1',
  133. 'nacelle_cab_temp_2','tower_cab_temp_1','tower_cab_temp_2','nacelle_ups_1','nacelle_ups_2','nacelle_ups_3','tower_ups_1','tower_ups_2','tower_ups_3','tower_ups_4' ],
  134. }
  135. },
  136. }
  137. # 嵌入源代码的MSET实现
  138. self.mset = self._create_mset_core()
  139. def _create_mset_core(self):
  140. """创建MSET核心计算模块"""
  141. class MSETCore:
  142. def __init__(self):
  143. self.matrixD = None
  144. self.normalDataBallTree = None
  145. self.healthyResidual = None
  146. def calcSimilarity(self, x, y):
  147. """优化后的相似度计算"""
  148. diff = np.array(x) - np.array(y)
  149. return 1 / (1 + np.sqrt(np.sum(diff ** 2)))
  150. def genDLMatrix(self, trainDataset, dataSize4D=15, dataSize4L=5):
  151. """优化矩阵生成过程"""
  152. m, n = trainDataset.shape
  153. # 快速选择极值点
  154. min_indices = np.argmin(trainDataset, axis=0)
  155. max_indices = np.argmax(trainDataset, axis=0)
  156. unique_indices = np.unique(np.concatenate([min_indices, max_indices]))
  157. self.matrixD = trainDataset[unique_indices].copy()
  158. # 快速填充剩余点
  159. remaining_indices = np.setdiff1d(np.arange(m), unique_indices)
  160. np.random.shuffle(remaining_indices)
  161. needed = max(0, dataSize4D - len(unique_indices))
  162. if needed > 0:
  163. self.matrixD = np.vstack([self.matrixD, trainDataset[remaining_indices[:needed]]])
  164. # 使用与源代码一致的BallTree参数
  165. self.normalDataBallTree = BallTree(
  166. self.matrixD,
  167. leaf_size=40,
  168. metric=lambda i, j: 1 - self.calcSimilarity(i, j) # 自定义相似度
  169. )
  170. # 使用所有数据计算残差
  171. self.healthyResidual = self.calcResidualByLocallyWeightedLR(trainDataset)
  172. return 0
  173. def calcResidualByLocallyWeightedLR(self, newStates):
  174. """优化残差计算"""
  175. if len(newStates.shape) == 1:
  176. newStates = newStates.reshape(-1, 1)
  177. dist, iList = self.normalDataBallTree.query(
  178. newStates,
  179. k=min(10, len(self.matrixD)),
  180. return_distance=True
  181. )
  182. weights = 1 / (dist + 1e-5)
  183. weights /= weights.sum(axis=1)[:, np.newaxis]
  184. est_X = np.sum(weights[:, :, np.newaxis] * self.matrixD[iList[0]], axis=1)
  185. return est_X - newStates
  186. def calcSPRT(self, newsStates, feature_weight, alpha=0.1, beta=0.1, decisionGroup=1):
  187. """优化SPRT计算"""
  188. stateResidual = self.calcResidualByLocallyWeightedLR(newsStates)
  189. weightedStateResidual = np.dot(stateResidual, feature_weight)
  190. weightedHealthyResidual = np.dot(self.healthyResidual, feature_weight)
  191. mu0 = np.mean(weightedHealthyResidual)
  192. sigma0 = np.std(weightedHealthyResidual)
  193. # 向量化计算
  194. n = len(newsStates)
  195. if n < decisionGroup:
  196. return [50] # 中性值
  197. rolling_mean = np.convolve(weightedStateResidual, np.ones(decisionGroup) / decisionGroup, 'valid')
  198. si = (rolling_mean - mu0) * (rolling_mean + mu0 - 2 * mu0) / (2 * sigma0 ** 2)
  199. lowThres = np.log(beta / (1 - alpha))
  200. highThres = np.log((1 - beta) / alpha)
  201. si = np.clip(si, lowThres, highThres)
  202. si = np.where(si > 0, si / highThres, si / lowThres)
  203. flag = 100 - si * 100
  204. # 填充不足的部分
  205. if len(flag) < n:
  206. flag = np.pad(flag, (0, n - len(flag)), mode='edge')
  207. return flag.tolist()
  208. def CRITIC_prepare(self, data, flag=1):
  209. """标准化处理"""
  210. data = data.astype(float)
  211. numeric_cols = data.select_dtypes(include=[np.number]).columns
  212. negative_cols = [col for col in numeric_cols
  213. if any(kw in col for kw in ['temperature'])]
  214. positive_cols = list(set(numeric_cols) - set(negative_cols))
  215. # 负向标准化
  216. if negative_cols:
  217. max_val = data[negative_cols].max()
  218. min_val = data[negative_cols].min()
  219. data[negative_cols] = (max_val - data[negative_cols]) / (max_val - min_val).replace(0, 1e-5)
  220. # 正向标准化
  221. if positive_cols:
  222. max_val = data[positive_cols].max()
  223. min_val = data[positive_cols].min()
  224. data[positive_cols] = (data[positive_cols] - min_val) / (max_val - min_val).replace(0, 1e-5)
  225. return data
  226. def CRITIC(self, data):
  227. """CRITIC权重计算(支持单特征)"""
  228. try:
  229. # 处理单特征情况
  230. if len(data.columns) == 1:
  231. return pd.Series([1.0], index=data.columns)
  232. data_norm = self.CRITIC_prepare(data.copy())
  233. std = data_norm.std(ddof=0).clip(lower=0.01)
  234. # 计算相关系数矩阵(添加异常处理)
  235. try:
  236. corr = np.abs(np.corrcoef(data_norm.T))
  237. np.fill_diagonal(corr, 0)
  238. conflict = np.sum(1 - corr, axis=1)
  239. except:
  240. # 如果计算相关系数失败,使用等权重
  241. return pd.Series(np.ones(len(data.columns))/len(data.columns))
  242. info = std * conflict
  243. weights = info / info.sum()
  244. return pd.Series(weights, index=data.columns)
  245. except Exception as e:
  246. print(f"CRITIC计算失败: {str(e)}")
  247. return pd.Series(np.ones(len(data.columns))/len(data.columns))
  248. def ahp(self, matrix):
  249. """AHP权重计算"""
  250. eigenvalue, eigenvector = np.linalg.eig(matrix)
  251. max_idx = np.argmax(eigenvalue)
  252. weight = eigenvector[:, max_idx].real
  253. return weight / weight.sum(), eigenvalue[max_idx].real
  254. return MSETCore()
  255. def assess_turbine(self, engine_code, data, mill_type, wind_turbine_name):
  256. """评估单个风机"""
  257. results = {
  258. "engine_code": engine_code,
  259. "wind_turbine_name": wind_turbine_name,
  260. "mill_type": mill_type,
  261. "total_health_score": None,
  262. "subsystems": {},
  263. "assessed_subsystems": []
  264. }
  265. # 各子系统评估
  266. subsystems_to_assess = [
  267. ('YawSystem', self.subsystem_config['YawSystem'], 2),
  268. ('PicthSystem', self.subsystem_config['PicthSystem'], 2),
  269. ('MainShaft', self.subsystem_config['MainShaft'], 2),
  270. ('Gearbox', self.subsystem_config['Gearbox'] if mill_type == 'dfig' else None, 2),
  271. ('Generator', self.subsystem_config['Generator'], 2),
  272. ('Converter', self.subsystem_config['Converter'], 2),
  273. ('HPU', self.subsystem_config['HPU'], 2),
  274. ('MCS', self.subsystem_config['MCS'], 2),
  275. ]
  276. for subsystem, config, min_features in subsystems_to_assess:
  277. if config is None:
  278. continue
  279. features = self._get_subsystem_features(config, data)
  280. # 功能1:无论特征数量是否足够都输出结果
  281. if len(features) >= min_features:
  282. assessment = self._assess_subsystem(data[features])
  283. else:
  284. assessment = {
  285. 'health_score': -1, # 特征不足时输出'-'
  286. 'weights': {},
  287. 'message': f'Insufficient features (required {min_features}, got {len(features)})'
  288. }
  289. print('结果打印',assessment)
  290. # 功能3:删除features内容
  291. if 'features' in assessment:
  292. del assessment['features']
  293. # 最终清理:确保没有NaN值
  294. for sys, result in results["subsystems"].items():
  295. if isinstance(result['health_score'], float) and np.isnan(result['health_score']):
  296. result['health_score'] = -1
  297. result['message'] = (result.get('message') or '') + '; NaN detected'
  298. if isinstance(results["total_health_score"], float) and np.isnan(results["total_health_score"]):
  299. results["total_health_score"] = -1
  300. results["subsystems"][subsystem] = assessment
  301. # 计算整机健康度(使用新字段名)
  302. if results["subsystems"]:
  303. # 只计算健康值为数字的子系统
  304. valid_subsystems = [
  305. k for k, v in results["subsystems"].items()
  306. if isinstance(v['health_score'], (int, float)) and v['health_score'] >= 0
  307. ]
  308. if valid_subsystems:
  309. weights = self._get_subsystem_weights(valid_subsystems)
  310. health_scores = [results["subsystems"][sys]['health_score'] for sys in valid_subsystems]
  311. results["total_health_score"] = float(np.dot(health_scores, weights))
  312. results["assessed_subsystems"] = valid_subsystems
  313. return results
  314. def _get_all_possible_features(self, mill_type, available_columns):
  315. """
  316. 获取所有可能的特征列
  317. 参数:
  318. mill_type: 机型类型
  319. available_columns: 数据库实际存在的列名列表
  320. """
  321. features = []
  322. available_columns_lower = [col.lower() for col in available_columns] # 不区分大小写匹配
  323. # for subsys_name, subsys_config in assessor.subsystem_config.items():
  324. for subsys_name, subsys_config in self.subsystem_config.items():
  325. # 处理子系统配置
  326. if subsys_name == 'generator':
  327. config = subsys_config.get(mill_type, {})
  328. elif subsys_name == 'drive_train' and mill_type != 'dfig':
  329. continue
  330. else:
  331. config = subsys_config
  332. # 处理固定特征
  333. if 'fixed' in config:
  334. for f in config['fixed']:
  335. if f in available_columns:
  336. features.append(f)
  337. # 处理关键词特征
  338. if 'keywords' in config:
  339. for rule in config['keywords']:
  340. matched = []
  341. include_kws = [kw.lower() for kw in rule['include']]
  342. exclude_kws = [ex.lower() for ex in rule.get('exclude', [])]
  343. for col in available_columns:
  344. col_lower = col.lower()
  345. # 检查包含关键词
  346. include_ok = all(kw in col_lower for kw in include_kws)
  347. # 检查排除关键词
  348. exclude_ok = not any(ex in col_lower for ex in exclude_kws)
  349. if include_ok and exclude_ok:
  350. matched.append(col)
  351. if len(matched) >= rule.get('min_count', 1):
  352. features.extend(matched)
  353. return list(set(features)) # 去重
  354. def _get_subsystem_features(self, config: Dict, data: pd.DataFrame) -> List[str]:
  355. """最终版特征获取方法"""
  356. available_features = []
  357. # 固定特征检查(要求至少10%非空)
  358. if 'fixed' in config:
  359. for f in config['fixed']:
  360. if f in data.columns and data[f].notna().mean() > 0.1:
  361. available_features.append(f)
  362. print(f"匹配到的固定特征: {available_features}")
  363. # 关键词特征检查
  364. if 'keywords' in config:
  365. for rule in config['keywords']:
  366. matched = [
  367. col for col in data.columns
  368. if all(kw.lower() in col.lower() for kw in rule['include'])
  369. and not any(ex.lower() in col.lower() for ex in rule.get('exclude', []))
  370. and data[col].notna().mean() > 0.1 # 数据有效性检查
  371. ]
  372. if len(matched) >= rule.get('min_count', 1):
  373. available_features.extend(matched)
  374. print(f"匹配到的关键词特征: {available_features}")
  375. return list(set(available_features))
  376. def _assess_subsystem(self, data: pd.DataFrame) -> Dict:
  377. """评估子系统(支持单特征)"""
  378. # 数据清洗
  379. clean_data = data.dropna()
  380. if len(clean_data) < 10: # 降低最小样本量要求(原为20)
  381. return {'health_score': -1, 'weights': {}, 'features': list(data.columns), 'message': 'Insufficient data'}
  382. try:
  383. # 标准化
  384. normalized_data = self.mset.CRITIC_prepare(clean_data)
  385. # 计算权重 - 处理单特征情况
  386. if len(normalized_data.columns) == 1:
  387. weights = pd.Series([1.0], index=normalized_data.columns)
  388. else:
  389. weights = self.mset.CRITIC(normalized_data)
  390. # MSET评估
  391. health_score = self._run_mset_assessment(normalized_data.values, weights.values)
  392. bins = [0, 10, 20, 30, 40, 50, 60, 70, 80]
  393. adjust_values = [87, 77, 67, 57, 47, 37, 27, 17, 7]
  394. # def adjust_score(score):
  395. # for i in range(len(bins)):
  396. # if score < bins[i]:
  397. # return score + adjust_values[i-1]
  398. # return score #
  399. # adjusted_score = adjust_score(health_score) #
  400. # if adjusted_score >= 100:
  401. # adjusted_score = 92.8
  402. return {
  403. 'health_score': float(health_score),
  404. 'weights': weights.to_dict(),
  405. 'features': list(data.columns)
  406. }
  407. except Exception as e:
  408. return {'health_score': -1, 'weights': {}, 'features': list(data.columns), 'message': str(e)}
  409. @lru_cache(maxsize=10)
  410. def _get_mset_model(self, train_data: tuple):
  411. """缓存MSET模型"""
  412. # 注意:由于lru_cache需要可哈希参数,这里使用元组
  413. arr = np.array(train_data)
  414. model = self._create_mset_core()
  415. model.genDLMatrix(arr)
  416. return model
  417. def _run_mset_assessment(self, data: np.ndarray, weights: np.ndarray) -> float:
  418. """执行MSET评估"""
  419. # 检查权重有效性
  420. if np.isnan(weights).any() or np.isinf(weights).any():
  421. weights = np.ones_like(weights) / len(weights) # 重置为等权重
  422. # 分割训练集和测试集
  423. split_idx = len(data) // 2
  424. train_data = data[:split_idx]
  425. test_data = data[split_idx:]
  426. # 使用缓存模型
  427. try:
  428. model = self._get_mset_model(tuple(map(tuple, train_data)))
  429. flags = model.calcSPRT(test_data, weights)
  430. # 过滤NaN值并计算均值
  431. valid_flags = [x for x in flags if not np.isnan(x)]
  432. if not valid_flags:
  433. return 50.0 # 默认中性值
  434. return float(np.mean(valid_flags))
  435. except Exception as e:
  436. print(f"MSET评估失败: {str(e)}")
  437. return 50.0 # 默认中性值
  438. def _get_subsystem_weights(self, subsystems: List[str]) -> np.ndarray:
  439. """生成等权重的子系统权重向量"""
  440. n = len(subsystems)
  441. if n == 0:
  442. return np.array([])
  443. # 直接返回等权重向量
  444. return np.ones(n) / n