cms_class_20241201.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. import numpy as np
  2. from scipy.signal import hilbert
  3. from scipy.fft import ifft
  4. import plotly.graph_objs as go
  5. import pandas as pd
  6. from sqlalchemy import create_engine, text
  7. import sqlalchemy
  8. from typing import Dict,Any
  9. import json
  10. import ast
  11. import math
  12. '''
  13. # 输入:
  14. {
  15. "ids":[12345,121212],
  16. "windCode":"xxxx",
  17. "analysisType":"xxxxx",
  18. "fmin":int(xxxx) (None),
  19. "fmax":"int(xxxx) (None),
  20. }
  21. [{id:xxxx,"time":xxx},{}]
  22. id[123456]
  23. # 通过id,读取mysql,获取数据
  24. engine = create_engine('mysql+pymysql://root:admin123456@192.168.50.235:30306/energy_data')
  25. def get_by_id(table_name,id):
  26. lastday_df_sql = f"SELECT * FROM {table_name} where id = {id} "
  27. # print(lastday_df_sql)
  28. df = pd.read_sql(lastday_df_sql, engine)
  29. return df
  30. select distinct id, timeStamp from table_name group by ids
  31. ids time
  32. 1 xxx
  33. 2 xxxx
  34. df_data = []
  35. # for id in ids:
  36. # sql_data = get_by_id('SKF001_wave',id)
  37. # df_data.append(sql_data)
  38. # print(sql_data)
  39. [df1,df2]
  40. '''
  41. '''
  42. 数据库字段:
  43. "samplingFrequency"
  44. "timeStamp"
  45. "mesureData"
  46. '''
  47. # %%
  48. # %%
  49. class CMSAnalyst:
  50. def __init__(self, fmin, fmax, table_name, ids):
  51. """
  52. table_name: 当前代码实际传入的是 windcode(例如 SKF001),内部会拼 _wave
  53. ids: [id1, id2, ...]
  54. """
  55. self.table_name = table_name
  56. self.ids = ids
  57. # 1) 从数据库获取原始数据(按 id 分组)
  58. self.datas = self._get_by_id(table_name, ids)
  59. if not self.datas:
  60. raise ValueError(f"[ERROR] 未查到任何数据 ids={ids}")
  61. # 2) 只保留需要字段
  62. self.datas = [
  63. df[['id', 'mesure_data', 'time_stamp', 'sampling_frequency',
  64. 'wind_turbine_number', 'rotational_speed', 'mesure_point_name']]
  65. for df in self.datas if df is not None and not df.empty
  66. ]
  67. if not self.datas:
  68. raise ValueError("[ERROR] 分组后 DataFrame 全为空")
  69. # 3) 单 id 情况:取第一个 df
  70. self.data_filter = self.datas[0]
  71. if self.data_filter.empty:
  72. raise ValueError("[ERROR] data_filter 为空,无法读取数据")
  73. # 4) 解析 mesure_data
  74. raw_md = self.data_filter['mesure_data'].iloc[0]
  75. self.data = self._parse_mesure_data(raw_md)
  76. if self.data is None or len(self.data) == 0:
  77. raise ValueError("[ERROR] mesure_data 解析失败或为空")
  78. self.data = np.asarray(self.data, dtype=float)
  79. # 5) 采样频率
  80. self.fs = int(self.data_filter['sampling_frequency'].iloc[0])
  81. # 6) 分析参数
  82. self.envelope_spectrum_m = self.data.shape[0]
  83. self.envelope_spectrum_n = 1
  84. self.envelope_spectrum_t = np.arange(self.envelope_spectrum_m) / self.fs
  85. self.fmin = fmin if fmin is not None else 0
  86. self.fmax = fmax if fmax is not None else float('inf')
  87. # 7) 设备信息
  88. self.wind_code = self.data_filter['wind_turbine_number'].iloc[0]
  89. self.rpm_Gen = self.data_filter['rotational_speed'].iloc[0]
  90. self.mesure_point_name = self.data_filter['mesure_point_name'].iloc[0]
  91. self.fn_Gen = round(self.rpm_Gen / 60, 2)
  92. # 8) 包络谱预计算
  93. self.envelope_spectrum_y = self._bandpass_filter(self.data)
  94. self.f, self.HP = self._calculate_envelope_spectrum(self.envelope_spectrum_y)
  95. # 9) 特征频率 & 轴承频率
  96. cf_dict = self.Characteristic_Frequency()
  97. self.CF = pd.DataFrame([cf_dict])
  98. n_rolls_m = self.CF['n_rolls'].iloc[0]
  99. d_rolls_m = self.CF['d_rolls'].iloc[0]
  100. D_diameter_m = self.CF['D_diameter'].iloc[0]
  101. theta_deg_m = self.CF['theta_deg'].iloc[0]
  102. if any(v is None for v in [n_rolls_m, d_rolls_m, D_diameter_m, theta_deg_m]):
  103. self.bearing_frequencies = None
  104. else:
  105. self.bearing_frequencies = self.calculate_bearing_frequencies(
  106. n_rolls_m, d_rolls_m, D_diameter_m, theta_deg_m, self.rpm_Gen
  107. )
  108. self.bearing_frequencies = pd.DataFrame([self.bearing_frequencies])
  109. # 10) 频谱预计算
  110. (
  111. self.frequency_domain_analysis_t,
  112. self.frequency_domain_analysis_f,
  113. self.frequency_domain_analysis_m,
  114. self.frequency_domain_analysis_mag,
  115. self.frequency_domain_analysis_Xrms,
  116. ) = self._calculate_spectrum(self.data)
  117. # 11) 时域时间轴
  118. self.time_domain_analysis_t = np.arange(self.data.shape[0]) / self.fs
  119. # ==========================================================
  120. # 工具:解析 mesure_data(兼容 json 字符串 / python list 字符串 / list)
  121. # ==========================================================
  122. def _parse_mesure_data(self, raw):
  123. if raw is None:
  124. return None
  125. # 已经是 list/np.array
  126. if isinstance(raw, (list, tuple, np.ndarray)):
  127. return list(raw)
  128. # 字符串:可能是 JSON 或 python list 字符串
  129. if isinstance(raw, str):
  130. s = raw.strip()
  131. # 优先 json.loads(如果是标准 JSON)
  132. try:
  133. v = json.loads(s)
  134. if isinstance(v, list):
  135. return v
  136. except Exception:
  137. pass
  138. # 再尝试 ast.literal_eval(如果是 python 格式 list)
  139. try:
  140. v = ast.literal_eval(s)
  141. if isinstance(v, (list, tuple, np.ndarray)):
  142. return list(v)
  143. except Exception:
  144. return None
  145. return None
  146. # ==========================================================
  147. # DB:按 id 拉取波形
  148. # ==========================================================
  149. def _get_by_id(self, windcode, ids):
  150. engine = create_engine('mysql+pymysql://root:admin123456@192.168.50.235:30306/energy_data_prod')
  151. table_name = f"{windcode}_wave"
  152. ids_str = ','.join(map(str, ids))
  153. sql = f"SELECT * FROM {table_name} WHERE id IN ({ids_str}) ORDER BY time_stamp"
  154. df = pd.read_sql(sql, engine)
  155. if df.empty:
  156. return []
  157. grouped = [group for _, group in df.groupby('id')]
  158. return grouped
  159. # ==========================================================
  160. # 包络谱:带通滤波(FFT 截频)
  161. # ==========================================================
  162. def _bandpass_filter(self, data):
  163. m = data.shape[0]
  164. # index 保护:fmin/fmax 可能超范围
  165. ni = int(round(self.fmin * m / self.fs + 1))
  166. ni = max(0, min(ni, m))
  167. if self.fmax == float('inf'):
  168. na = m
  169. else:
  170. na = int(round(self.fmax * m / self.fs + 1))
  171. na = max(0, min(na, m))
  172. if na <= ni:
  173. # 退化情况:不做滤波
  174. y = np.zeros((m, 1))
  175. y[:, 0] = data
  176. return y
  177. z = np.fft.fft(data)
  178. a = np.zeros(m, dtype=complex)
  179. a[ni:na] = z[ni:na]
  180. # 对称频段
  181. a[m - na + 1: m - ni + 1] = z[m - na + 1: m - ni + 1]
  182. x_ifft = np.fft.ifft(a)
  183. y = np.zeros((m, 1))
  184. y[:, 0] = np.real(x_ifft)
  185. return y
  186. def _calculate_envelope_spectrum(self, y):
  187. m, n = y.shape
  188. HP = np.zeros((m, n))
  189. for p in range(n):
  190. H = np.abs(hilbert(y[:, p] - np.mean(y[:, p])))
  191. HP[:, p] = np.abs(np.fft.fft(H - np.mean(H))) * 2 / m
  192. f = np.fft.fftfreq(m, d=1 / self.fs)
  193. return f, HP
  194. def envelope_spectrum(self):
  195. positive_frequencies = self.f[: self.envelope_spectrum_m // 2]
  196. positive_HP = self.HP[: self.envelope_spectrum_m // 2, 0]
  197. x = positive_frequencies
  198. y = positive_HP
  199. Xrms = float(np.sqrt(np.mean(y ** 2)))
  200. rpm_Gen = round(float(self.rpm_Gen), 2)
  201. if self.bearing_frequencies is None:
  202. BPFI_1X = BPFO_1X = BSF_1X = FTF_1X = None
  203. else:
  204. BPFI_1X = round(float(self.bearing_frequencies['BPFI'].iloc[0]), 2)
  205. BPFO_1X = round(float(self.bearing_frequencies['BPFO'].iloc[0]), 2)
  206. BSF_1X = round(float(self.bearing_frequencies['BSF'].iloc[0]), 2)
  207. FTF_1X = round(float(self.bearing_frequencies['FTF'].iloc[0]), 2)
  208. fn_Gen = round(float(self.fn_Gen), 2)
  209. _3P_1X = fn_Gen * 3
  210. result = {
  211. "fs": int(self.fs),
  212. "Xrms": round(Xrms, 2),
  213. "x": list(x),
  214. "y": list(y),
  215. "title": "包络谱",
  216. "xaxis": "频率(Hz)",
  217. "yaxis": "加速度(m/s^2)",
  218. "rpm_Gen": rpm_Gen,
  219. "BPFI": [{"Xaxis": (None if BPFI_1X is None else BPFI_1X * k), "val": f"{k}BPFI"} for k in range(1, 7)],
  220. "BPFO": [{"Xaxis": (None if BPFO_1X is None else BPFO_1X * k), "val": f"{k}BPFO"} for k in range(1, 7)],
  221. "BSF": [{"Xaxis": (None if BSF_1X is None else BSF_1X * k), "val": f"{k}BSF"} for k in range(1, 7)],
  222. "FTF": [{"Xaxis": (None if FTF_1X is None else FTF_1X * k), "val": f"{k}FTF"} for k in range(1, 7)],
  223. "fn_Gen": [{"Xaxis": fn_Gen * k, "val": f"{k}X"} for k in range(1, 7)],
  224. "B3P": _3P_1X,
  225. }
  226. return self.replace_nan(result)
  227. # ==========================================================
  228. # 频谱
  229. # ==========================================================
  230. def _calculate_spectrum(self, data):
  231. m = data.shape[0]
  232. t = np.arange(m) / self.fs
  233. mag = np.abs(np.fft.fft(data - np.mean(data))) * 2 / m
  234. f = np.fft.fftfreq(m, d=1 / self.fs)
  235. Xrms = float(np.sqrt(np.mean(data ** 2)))
  236. return t, f, m, mag, Xrms
  237. def frequency_domain(self):
  238. positive_frequencies = self.frequency_domain_analysis_f[: self.frequency_domain_analysis_m // 2]
  239. positive_mag = self.frequency_domain_analysis_mag[: self.frequency_domain_analysis_m // 2]
  240. x = positive_frequencies
  241. y = positive_mag
  242. Xrms = float(self.frequency_domain_analysis_Xrms)
  243. rpm_Gen = round(float(self.rpm_Gen), 2)
  244. if self.bearing_frequencies is None:
  245. BPFI_1X = BPFO_1X = BSF_1X = FTF_1X = None
  246. else:
  247. BPFI_1X = round(float(self.bearing_frequencies['BPFI'].iloc[0]), 2)
  248. BPFO_1X = round(float(self.bearing_frequencies['BPFO'].iloc[0]), 2)
  249. BSF_1X = round(float(self.bearing_frequencies['BSF'].iloc[0]), 2)
  250. FTF_1X = round(float(self.bearing_frequencies['FTF'].iloc[0]), 2)
  251. fn_Gen = round(float(self.fn_Gen), 2)
  252. _3P_1X = fn_Gen * 3
  253. result = {
  254. "fs": int(self.fs),
  255. "Xrms": round(Xrms, 2),
  256. "x": list(x),
  257. "y": list(y),
  258. "title": "频域信号",
  259. "xaxis": "频率(Hz)",
  260. "yaxis": "加速度(m/s^2)",
  261. "rpm_Gen": rpm_Gen,
  262. "BPFI": [{"Xaxis": (None if BPFI_1X is None else BPFI_1X * k), "val": f"{k}BPFI"} for k in range(1, 7)],
  263. "BPFO": [{"Xaxis": (None if BPFO_1X is None else BPFO_1X * k), "val": f"{k}BPFO"} for k in range(1, 7)],
  264. "BSF": [{"Xaxis": (None if BSF_1X is None else BSF_1X * k), "val": f"{k}BSF"} for k in range(1, 7)],
  265. "FTF": [{"Xaxis": (None if FTF_1X is None else FTF_1X * k), "val": f"{k}FTF"} for k in range(1, 7)],
  266. "fn_Gen": [{"Xaxis": fn_Gen * k, "val": f"{k}X"} for k in range(1, 7)],
  267. "B3P": _3P_1X,
  268. }
  269. result = self.replace_nan(result)
  270. return json.dumps(result, ensure_ascii=False)
  271. # ==========================================================
  272. # 时域
  273. # ==========================================================
  274. def time_domain(self):
  275. x = self.time_domain_analysis_t
  276. y = self.data
  277. mean_value = float(np.mean(y))
  278. max_value = float(np.max(y))
  279. min_value = float(np.min(y))
  280. Xrms = float(np.sqrt(np.mean(y ** 2)))
  281. Xp = float((max_value - min_value) / 2)
  282. Xpp = float(max_value - min_value)
  283. Cf = (Xp / Xrms) if Xrms != 0 else 0.0
  284. Sf = (Xrms / mean_value) if mean_value != 0 else 0.0
  285. If = (Xp / float(np.mean(np.abs(y)))) if np.mean(np.abs(y)) != 0 else 0.0
  286. Xr = float(np.mean(np.sqrt(np.abs(y))) ** 2)
  287. Ce = (Xp / Xr) if Xr != 0 else 0.0
  288. # 偏度/峭度
  289. Cw = float(np.mean((np.abs(y) - mean_value) ** 3) / (Xrms ** 3)) if Xrms != 0 else 0.0
  290. Cq = float(np.mean((np.abs(y) - mean_value) ** 4) / (Xrms ** 4)) if Xrms != 0 else 0.0
  291. result = {
  292. "x": list(x),
  293. "y": list(y),
  294. "title": "时间域信号",
  295. "xaxis": "时间(s)",
  296. "yaxis": "加速度(m/s^2)",
  297. "fs": int(self.fs),
  298. "Xrms": round(Xrms, 2),
  299. "mean_value": round(mean_value, 2),
  300. "max_value": round(max_value, 2),
  301. "min_value": round(min_value, 2),
  302. "Xp": round(Xp, 2),
  303. "Xpp": round(Xpp, 2),
  304. "Cf": round(Cf, 2),
  305. "Sf": round(Sf, 2),
  306. "If": round(If, 2),
  307. "Ce": round(Ce, 2),
  308. "Cw": round(Cw, 2),
  309. "Cq": round(Cq, 2),
  310. "rpm_Gen": round(float(self.rpm_Gen), 2),
  311. }
  312. result = self.replace_nan(result)
  313. return json.dumps(result, ensure_ascii=False)
  314. # ==========================================================
  315. # 趋势分析(按id统计一条数据,取每个id的第一条)
  316. # ==========================================================
  317. def trend_analysis(self) -> str:
  318. combined_df = pd.concat(self.datas, ignore_index=True)
  319. if combined_df.empty:
  320. return json.dumps([], ensure_ascii=False)
  321. # 统一解析 mesure_data
  322. def parse_cell(x):
  323. v = self._parse_mesure_data(x)
  324. return v
  325. combined_df['parsed_data'] = combined_df['mesure_data'].apply(parse_cell)
  326. def calculate_stats(group: pd.DataFrame) -> Optional[Dict[str, Any]]:
  327. if group.empty:
  328. return None
  329. arr = group['parsed_data'].iloc[0]
  330. if arr is None or len(arr) == 0:
  331. return None
  332. data = np.asarray(arr, dtype=float)
  333. fs = int(group['sampling_frequency'].iloc[0])
  334. dt = 1 / fs
  335. mean = float(np.mean(data))
  336. max_val = float(np.max(data))
  337. min_val = float(np.min(data))
  338. Xrms = float(np.sqrt(np.mean(data ** 2)))
  339. Xp = float((max_val - min_val) / 2)
  340. Cf = (Xp / Xrms) if Xrms != 0 else 0.0
  341. Sf = (Xrms / mean) if mean != 0 else 0.0
  342. If = (Xp / float(np.mean(np.abs(data)))) if np.mean(np.abs(data)) != 0 else 0.0
  343. Xr = float(np.mean(np.sqrt(np.abs(data))) ** 2)
  344. Ce = (Xp / Xr) if Xr != 0 else 0.0
  345. # 速度有效值
  346. velocity = np.cumsum(data) * dt
  347. velocity_rms = float(np.sqrt(np.mean(velocity ** 2)))
  348. Cw = float(np.mean((data - mean) ** 3) / (Xrms ** 3)) if Xrms != 0 else 0.0
  349. Cq = float(np.mean((data - mean) ** 4) / (Xrms ** 4)) if Xrms != 0 else 0.0
  350. return {
  351. "fs": fs,
  352. "Mean": round(mean, 2),
  353. "Max": round(max_val, 2),
  354. "Min": round(min_val, 2),
  355. "Xrms": round(Xrms, 2),
  356. "Xp": round(Xp, 2),
  357. "If": round(If, 2),
  358. "Cf": round(Cf, 2),
  359. "Sf": round(Sf, 2),
  360. "Ce": round(Ce, 2),
  361. "Cw": round(Cw, 2),
  362. "Cq": round(Cq, 2),
  363. "velocity_rms": round(velocity_rms, 2),
  364. "time_stamp": str(group['time_stamp'].iloc[0]),
  365. "id": int(group['id'].iloc[0]),
  366. }
  367. stats = [
  368. s for s in combined_df.groupby('id', sort=True).apply(calculate_stats).tolist()
  369. if s is not None
  370. ]
  371. stats = self.replace_nan(stats)
  372. return json.dumps(stats, ensure_ascii=False)
  373. # ==========================================================
  374. # 特征频率
  375. # ==========================================================
  376. def Characteristic_Frequency(self):
  377. """
  378. 目标:拿到 _brand/_model -> unit_dict_brand_model -> rolls_number 等参数
  379. 任意失败:返回 None 字段(不会炸)
  380. """
  381. def empty_result():
  382. return {
  383. "type": "bearing",
  384. "n_rolls": None,
  385. "d_rolls": None,
  386. "D_diameter": None,
  387. "theta_deg": None,
  388. }
  389. str1 = str(self.mesure_point_name or "")
  390. engine_code = str(self.wind_code or "")
  391. Engine = create_engine('mysql+pymysql://admin:admin123456@192.168.50.233:3306/energy_show')
  392. df2 = pd.read_sql(
  393. f"SELECT * FROM wind_engine_group WHERE engine_code = '{engine_code}'",
  394. Engine
  395. )
  396. if df2.empty or 'mill_type_code' not in df2.columns:
  397. return empty_result()
  398. mill_type_code = df2['mill_type_code'].iloc[0]
  399. _brand = None
  400. _model = None
  401. # --------------------------
  402. # main_bearing
  403. # --------------------------
  404. if 'main_bearing' in str1:
  405. df3 = pd.read_sql(
  406. f"SELECT * FROM unit_bearings WHERE mill_type_code = '{mill_type_code}'",
  407. Engine
  408. )
  409. if df3.empty:
  410. return empty_result()
  411. # front/rear/自动选择
  412. if 'front' in str1:
  413. brand_col = 'front_bearing_brand'
  414. model_col = 'front_bearing_model'
  415. elif 'rear' in str1:
  416. brand_col = 'rear_bearing_brand'
  417. model_col = 'rear_bearing_model'
  418. else:
  419. candidates = [
  420. ('front_bearing_brand', 'front_bearing_model'),
  421. ('rear_bearing_brand', 'rear_bearing_model')
  422. ]
  423. brand_col = model_col = None
  424. for b, m in candidates:
  425. if b in df3.columns and m in df3.columns:
  426. if pd.notna(df3[b].iloc[0]) and pd.notna(df3[m].iloc[0]):
  427. brand_col, model_col = b, m
  428. break
  429. if brand_col is None:
  430. return empty_result()
  431. if brand_col not in df3.columns or model_col not in df3.columns:
  432. return empty_result()
  433. _brand = df3[brand_col].iloc[0]
  434. _model = df3[model_col].iloc[0]
  435. # --------------------------
  436. # generator / stator
  437. # --------------------------
  438. elif 'generator' in str1 or 'stator' in str1:
  439. df3 = pd.read_sql(
  440. f"SELECT * FROM unit_dynamo WHERE mill_type_code = '{mill_type_code}'",
  441. Engine
  442. )
  443. if df3.empty:
  444. return empty_result()
  445. if 'non' in str1:
  446. brand_col = 'non_drive_end_bearing_brand'
  447. model_col = 'non_drive_end_bearing_model'
  448. else:
  449. brand_col = 'drive_end_bearing_brand'
  450. model_col = 'drive_end_bearing_model'
  451. if brand_col not in df3.columns or model_col not in df3.columns:
  452. return empty_result()
  453. _brand = df3[brand_col].iloc[0]
  454. _model = df3[model_col].iloc[0]
  455. # --------------------------
  456. # gearbox
  457. # --------------------------
  458. elif 'gearbox' in str1:
  459. df3 = pd.read_sql(
  460. f"SELECT * FROM unit_gearbox WHERE mill_type_code = '{mill_type_code}'",
  461. Engine
  462. )
  463. if df3.empty or 'code' not in df3.columns:
  464. return empty_result()
  465. gearbox_code = df3['code'].iloc[0]
  466. # 行星轮/太阳轮:unit_gearbox_structure
  467. if ('planet' in str1) or ('sun' in str1):
  468. gearbox_structure = 1 if 'planet' in str1 else 2
  469. planetary_gear_grade = 1
  470. if 'second' in str1:
  471. planetary_gear_grade = 2
  472. elif 'third' in str1:
  473. planetary_gear_grade = 3
  474. df33 = pd.read_sql(
  475. f"""
  476. SELECT bearing_brand, bearing_model
  477. FROM unit_gearbox_structure
  478. WHERE gearbox_code = '{gearbox_code}'
  479. AND gearbox_structure = '{gearbox_structure}'
  480. AND planetary_gear_grade = '{planetary_gear_grade}'
  481. """,
  482. Engine
  483. )
  484. if df33.empty:
  485. return empty_result()
  486. if 'bearing_brand' not in df33.columns or 'bearing_model' not in df33.columns:
  487. return empty_result()
  488. _brand = df33['bearing_brand'].iloc[0]
  489. _model = df33['bearing_model'].iloc[0]
  490. # 轴承:unit_gearbox_bearings
  491. elif ('shaft' in str1) or ('input' in str1) or ('low_speed' in str1) or ('high_speed' in str1):
  492. parallel_wheel_grade = 1
  493. if 'low_speed_intermediate' in str1:
  494. parallel_wheel_grade = 2
  495. elif 'high_speed' in str1:
  496. parallel_wheel_grade = 3
  497. df33 = pd.read_sql(
  498. f"""
  499. SELECT bearing_rs_brand, bearing_rs_model, bearing_gs_brand, bearing_gs_model, bearing_brand, bearing_model
  500. FROM unit_gearbox_bearings
  501. WHERE gearbox_code = '{gearbox_code}'
  502. AND parallel_wheel_grade = '{parallel_wheel_grade}'
  503. """,
  504. Engine
  505. )
  506. if df33.empty:
  507. return empty_result()
  508. # 高速/中间优先 rs/gs;低速取 bearing_brand/model
  509. if ('high_speed' in str1) or ('low_speed_intermediate' in str1):
  510. # rs/gs 选有值的
  511. candidates = [
  512. ('bearing_rs_brand', 'bearing_rs_model'),
  513. ('bearing_gs_brand', 'bearing_gs_model')
  514. ]
  515. for b, m in candidates:
  516. if b in df33.columns and m in df33.columns:
  517. if pd.notna(df33[b].iloc[0]) and pd.notna(df33[m].iloc[0]):
  518. _brand = df33[b].iloc[0]
  519. _model = df33[m].iloc[0]
  520. break
  521. if _brand is None:
  522. return empty_result()
  523. else:
  524. # low_speed:bearing_brand/model
  525. if 'bearing_brand' in df33.columns and 'bearing_model' in df33.columns:
  526. if pd.notna(df33['bearing_brand'].iloc[0]) and pd.notna(df33['bearing_model'].iloc[0]):
  527. _brand = df33['bearing_brand'].iloc[0]
  528. _model = df33['bearing_model'].iloc[0]
  529. else:
  530. return empty_result()
  531. else:
  532. return empty_result()
  533. else:
  534. return empty_result()
  535. else:
  536. # 其它测点:直接返回空,不炸
  537. return empty_result()
  538. # 最终检查
  539. if _brand is None or _model is None or (pd.isna(_brand) or pd.isna(_model)):
  540. return empty_result()
  541. # --------------------------
  542. # unit_dict_brand_model 查询
  543. # --------------------------
  544. df4 = pd.read_sql(
  545. "SELECT * FROM unit_dict_brand_model WHERE manufacture = %s AND model_number = %s",
  546. Engine,
  547. params=(str(_brand), str(_model))
  548. )
  549. if df4.empty:
  550. return empty_result()
  551. # 字段安全读取
  552. needed = ['rolls_number', 'rolls_diameter', 'circle_diameter', 'theta_deg']
  553. for col in needed:
  554. if col not in df4.columns:
  555. return empty_result()
  556. return {
  557. "type": "bearing",
  558. "n_rolls": None if pd.isna(df4['rolls_number'].iloc[0]) else float(df4['rolls_number'].iloc[0]),
  559. "d_rolls": None if pd.isna(df4['rolls_diameter'].iloc[0]) else float(df4['rolls_diameter'].iloc[0]),
  560. "D_diameter": None if pd.isna(df4['circle_diameter'].iloc[0]) else float(df4['circle_diameter'].iloc[0]),
  561. "theta_deg": None if pd.isna(df4['theta_deg'].iloc[0]) else float(df4['theta_deg'].iloc[0]),
  562. }
  563. # ==========================================================
  564. # 轴承频率公式
  565. # ==========================================================
  566. def calculate_bearing_frequencies(self, n, d, D, theta_deg, rpm):
  567. theta = math.radians(theta_deg)
  568. ratio = d / D
  569. f_r = rpm / 60.0
  570. BPFI = n / 2 * (1 + ratio * math.cos(theta)) * f_r
  571. BPFO = n / 2 * (1 - ratio * math.cos(theta)) * f_r
  572. BSF = (D / (2 * d)) * (1 - (ratio ** 2) * (math.cos(theta) ** 2)) * f_r
  573. FTF = 0.5 * (1 - ratio * math.cos(theta)) * f_r
  574. return {
  575. "BPFI": round(float(BPFI), 2),
  576. "BPFO": round(float(BPFO), 2),
  577. "BSF": round(float(BSF), 2),
  578. "FTF": round(float(FTF), 2),
  579. }
  580. # ==========================================================
  581. # NaN 替换
  582. # ==========================================================
  583. def replace_nan(self, obj):
  584. if isinstance(obj, dict):
  585. return {k: self.replace_nan(v) for k, v in obj.items()}
  586. if isinstance(obj, list):
  587. return [self.replace_nan(x) for x in obj]
  588. if isinstance(obj, float) and math.isnan(obj):
  589. return None
  590. return obj
  591. if __name__ == "__main__":
  592. # table_name = "SKF001_wave"
  593. # ids = [67803,67804]
  594. # fmin, fmax = None, None
  595. cms = CMSAnalyst(fmin, fmax,table_name,ids)
  596. time_domain = cms.time_domain()
  597. # print(time_domain)
  598. '''
  599. trace = go.Scatter(
  600. x=time_domain['x'],
  601. y=time_domain['y'],
  602. mode="lines",
  603. name=time_domain['title'],
  604. )
  605. layout = go.Layout(
  606. title= time_domain['title'],
  607. xaxis=dict(title=time_domain["xaxis"]),
  608. yaxis=dict(title=time_domain["yaxis"]),
  609. )
  610. fig = go.Figure(data=[trace], layout=layout)
  611. fig.show()
  612. '''
  613. # data_path_lsit = ["test1.csv", "test2.csv"]
  614. # trend_analysis_test = cms.trend_analysis(data_path_lsit, fmin, fmax)
  615. # print(trend_analysis_test)