baseAnalyst.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. # baseAnalyst.py
  2. import concurrent.futures
  3. import logging
  4. from abc import ABC, abstractmethod
  5. from logging import Logger
  6. from typing import Any, Tuple
  7. from urllib.parse import quote
  8. import numpy as np
  9. import pandas as pd
  10. import traceback
  11. from algorithmContract.confBusiness import *
  12. from algorithmContract.configAnalysis import ConfigAnalysis
  13. from algorithmContract.contract import Contract
  14. from behavior.dataMarker import DataMarker
  15. from common.appConfig import GetBusinessDbUtil
  16. from common.commonBusiness import CommonBusiness
  17. from numpy.typing import NDArray
  18. from sqlalchemy.sql import text
  19. from utils.directoryUtil import DirectoryUtil as dir
  20. from utils.minioUtil.threadSafeMinioClient import ThreadSafeMinioClient
  21. from utils.rdbmsUtil.databaseUtil import DatabaseUtil
  22. class BaseAnalyst(ABC):
  23. def __init__(self, logger: Logger, dbUtil: dict[str, DatabaseUtil], minioUtil: ThreadSafeMinioClient, conf: Contract,
  24. powerFarmInfo: pd.DataFrame, turbineInfo: pd.DataFrame, turbineModelInfo: pd.DataFrame, dataTransfer: pd.DataFrame,
  25. weatherStationInfo: pd.DataFrame, dataFrameContractOfTurbine: pd.DataFrame):
  26. self.logger = logger
  27. self.dbUtil = dbUtil
  28. self.minioUtil = minioUtil
  29. self.conf = conf
  30. # 定义风速区间
  31. self.binsWindSpeed: NDArray[np.floating[Any]] = np.arange(0, 26, 0.5)
  32. self.dataMarker = DataMarker()
  33. self.common = CommonBusiness()
  34. self.customFilterStatusOfTurbine = "valueTurbineStatus"
  35. self.customFilterPitchAngle = "valuePitchAngle"
  36. self.customFilterWindSpeed = "valueWindSpeed"
  37. self.customFilterActivePower = "valueActivePower"
  38. self.customFilterGeneratorSpeed = "valueGeneratorSpeed"
  39. # 加载所有新能源场站信息
  40. self.powerFarmInfo = powerFarmInfo
  41. self.currPowerFarmInfo = self.common.getPowerFarm(
  42. self.conf.dataContract.dataFilter.powerFarmID, self.powerFarmInfo)
  43. # 加载所有风电机组信息
  44. self.turbineInfo = turbineInfo
  45. # 加载数据转换信息
  46. # self.dataTransfer = dataTransfer
  47. # 加载机型信息
  48. self.turbineModelInfo = turbineModelInfo
  49. # 加载所有测风塔信息
  50. self.weatherStationInfo = weatherStationInfo
  51. # 加载所有新能源场站,及所属风电机组机型的合同功率曲线
  52. self.dataFrameContractOfTurbine = dataFrameContractOfTurbine
  53. # 图表 轴系设置
  54. # 常值
  55. self.RatedPowerOfTurbine = self.turbineInfo[Field_RatedPower].iloc[0]
  56. # 图表 轴系设置
  57. # 轴系 发电机转速
  58. iGeneratorSpeed = "IgeneratorSpeed"
  59. dGeneratorSpeed = "DgeneratorSpeed"
  60. if self.turbineModelInfo[Field_MotionType] .iloc[0] == 2:
  61. # 直驱 发电机转速
  62. self.axisStepGeneratorSpeed = conf.dataContract.graphSets[dGeneratorSpeed].step if not self.common.isNone(
  63. conf.dataContract.graphSets[dGeneratorSpeed]) and not self.common.isNone(
  64. conf.dataContract.graphSets[dGeneratorSpeed].step) else 1
  65. self.axisLowerLimitGeneratorSpeed = conf.dataContract.graphSets[dGeneratorSpeed].min if not self.common.isNone(
  66. conf.dataContract.graphSets[dGeneratorSpeed].min) else 5
  67. self.axisUpperLimitGeneratorSpeed = conf.dataContract.graphSets[dGeneratorSpeed].max if not self.common.isNone(
  68. conf.dataContract.graphSets[dGeneratorSpeed].max) else 25
  69. else:
  70. # 非直驱 发电机转速
  71. self.axisStepGeneratorSpeed = conf.dataContract.graphSets[iGeneratorSpeed].step if not self.common.isNone(
  72. conf.dataContract.graphSets[iGeneratorSpeed]) and not self.common.isNone(
  73. conf.dataContract.graphSets[iGeneratorSpeed].step) else 200
  74. self.axisLowerLimitGeneratorSpeed = conf.dataContract.graphSets[iGeneratorSpeed].min if not self.common.isNone(
  75. conf.dataContract.graphSets[iGeneratorSpeed].min) else 1000
  76. self.axisUpperLimitGeneratorSpeed = conf.dataContract.graphSets[iGeneratorSpeed].max if not self.common.isNone(
  77. conf.dataContract.graphSets[iGeneratorSpeed].max) else 2000
  78. # --------以下为原写法,未区分驱动方式直接选择了非直驱--------
  79. # # 直驱 发电机转速
  80. # self.axisStepGeneratorSpeedWithDirect = conf.dataContract.graphSets[dGeneratorSpeed].step if not self.common.isNone(
  81. # conf.dataContract.graphSets[dGeneratorSpeed]) and not self.common.isNone(
  82. # conf.dataContract.graphSets[dGeneratorSpeed].step) else 5
  83. # self.axisLowerLimitGeneratorSpeedWithDirect = conf.dataContract.graphSets[dGeneratorSpeed].min if not self.common.isNone(
  84. # conf.dataContract.graphSets[dGeneratorSpeed].min) else 0
  85. # self.axisUpperLimitGeneratorSpeedWithDirect = conf.dataContract.graphSets[dGeneratorSpeed].max if not self.common.isNone(
  86. # conf.dataContract.graphSets[dGeneratorSpeed].max) else 30
  87. # # 非直驱 发电机转速
  88. # self.axisStepGeneratorSpeed = conf.dataContract.graphSets[iGeneratorSpeed].step if not self.common.isNone(
  89. # conf.dataContract.graphSets[iGeneratorSpeed]) and not self.common.isNone(
  90. # conf.dataContract.graphSets[iGeneratorSpeed].step) else 200
  91. # self.axisLowerLimitGeneratorSpeed = conf.dataContract.graphSets[iGeneratorSpeed].min if not self.common.isNone(
  92. # conf.dataContract.graphSets[iGeneratorSpeed].min) else 1000
  93. # self.axisUpperLimitGeneratorSpeed = conf.dataContract.graphSets[iGeneratorSpeed].max if not self.common.isNone(
  94. # conf.dataContract.graphSets[iGeneratorSpeed].max) else 2000
  95. # 轴系 发电机转矩
  96. iGgeneratorTorque = "IgeneratorTorque"
  97. dGgeneratorTorque = "DgeneratorTorque"
  98. # if turbineModelInfo[Field_MotionType].iloc[0]==2 :
  99. # # 直驱 发电机转矩
  100. # self.axisStepGeneratorTorque = conf.dataContract.graphSets[dGgeneratorTorque].step if not self.common.isNone(
  101. # conf.dataContract.graphSets[dGgeneratorTorque]) and not self.common.isNone(
  102. # conf.dataContract.graphSets[dGgeneratorTorque].step) else 10000
  103. # self.axisLowerLimitGeneratorTorque = conf.dataContract.graphSets[dGgeneratorTorque].min if not self.common.isNone(
  104. # conf.dataContract.graphSets[dGgeneratorTorque].min) else 0
  105. # self.axisUpperLimitGeneratorTorque = conf.dataContract.graphSets[dGgeneratorTorque].max if not self.common.isNone(
  106. # conf.dataContract.graphSets[dGgeneratorTorque].max) else 100000
  107. # else:
  108. # # 非直驱 发电机转矩
  109. # self.axisStepGeneratorTorque = conf.dataContract.graphSets[iGgeneratorTorque].step if not self.common.isNone(
  110. # conf.dataContract.graphSets[iGgeneratorTorque]) and not self.common.isNone(
  111. # conf.dataContract.graphSets[iGgeneratorTorque].step) else 2000
  112. # self.axisLowerLimitGeneratorTorque = conf.dataContract.graphSets[iGgeneratorTorque].min if not self.common.isNone(
  113. # conf.dataContract.graphSets[iGgeneratorTorque].min) else 0
  114. # self.axisUpperLimitGeneratorTorque = conf.dataContract.graphSets[iGgeneratorTorque].max if not self.common.isNone(
  115. # conf.dataContract.graphSets[iGgeneratorTorque].max) else 12000
  116. # --------以下为原写法,未区分驱动方式直接选择了非直驱--------
  117. # 直驱 发电机转矩
  118. self.axisStepGeneratorTorqueWithDirect = conf.dataContract.graphSets[dGgeneratorTorque].step if not self.common.isNone(
  119. conf.dataContract.graphSets[dGgeneratorTorque]) and not self.common.isNone(
  120. conf.dataContract.graphSets[dGgeneratorTorque].step) else 10000
  121. self.axisLowerLimitGeneratorTorqueWithDirect = conf.dataContract.graphSets[dGgeneratorTorque].min if not self.common.isNone(
  122. conf.dataContract.graphSets[dGgeneratorTorque].min) else 0
  123. self.axisUpperLimitGeneratorTorqueWithDirect = conf.dataContract.graphSets[dGgeneratorTorque].max if not self.common.isNone(
  124. conf.dataContract.graphSets[dGgeneratorTorque].max) else 100000
  125. # 非直驱 发电机转矩
  126. self.axisStepGeneratorTorque = conf.dataContract.graphSets[iGgeneratorTorque].step if not self.common.isNone(
  127. conf.dataContract.graphSets[iGgeneratorTorque]) and not self.common.isNone(
  128. conf.dataContract.graphSets[iGgeneratorTorque].step) else 2000
  129. self.axisLowerLimitGeneratorTorque = conf.dataContract.graphSets[iGgeneratorTorque].min if not self.common.isNone(
  130. conf.dataContract.graphSets[iGgeneratorTorque].min) else 0
  131. self.axisUpperLimitGeneratorTorque = conf.dataContract.graphSets[iGgeneratorTorque].max if not self.common.isNone(
  132. conf.dataContract.graphSets[iGgeneratorTorque].max) else 12000
  133. # 轴系 有功功率
  134. activePower = "activePower"
  135. self.axisStepActivePower = conf.dataContract.graphSets[activePower].step if not self.common.isNone(
  136. conf.dataContract.graphSets[activePower]) and not self.common.isNone(
  137. conf.dataContract.graphSets[activePower].step) else 250
  138. self.axisLowerLimitActivePower = conf.dataContract.graphSets[activePower].min if not self.common.isNone(
  139. conf.dataContract.graphSets[activePower].min) else 0
  140. self.axisUpperLimitActivePower = conf.dataContract.graphSets[activePower].max if not self.common.isNone(
  141. conf.dataContract.graphSets[activePower].max) else self.RatedPowerOfTurbine*1.2
  142. # 轴系 桨距角
  143. pitchAngle = "pitchAngle"
  144. self.axisStepPitchAngle = conf.dataContract.graphSets[pitchAngle].step if not self.common.isNone(
  145. conf.dataContract.graphSets[pitchAngle]) and not self.common.isNone(
  146. conf.dataContract.graphSets[pitchAngle].step) else 2
  147. self.axisLowerLimitPitchAngle = conf.dataContract.graphSets[pitchAngle].min if not self.common.isNone(
  148. conf.dataContract.graphSets[pitchAngle].min) else -2
  149. self.axisUpperLimitPitchAngle = conf.dataContract.graphSets[pitchAngle].max if not self.common.isNone(
  150. conf.dataContract.graphSets[pitchAngle].max) else 28
  151. # 轴系 风能利用系数
  152. cp = "cp"
  153. self.axisStepCp = conf.dataContract.graphSets[cp].step if not self.common.isNone(
  154. conf.dataContract.graphSets[cp]) and not self.common.isNone(
  155. conf.dataContract.graphSets[cp].step) else 0.1
  156. self.axisLowerLimitCp = conf.dataContract.graphSets[cp].min if not self.common.isNone(
  157. conf.dataContract.graphSets[cp].min) else 0
  158. self.axisUpperLimitCp = conf.dataContract.graphSets[cp].max if not self.common.isNone(
  159. conf.dataContract.graphSets[cp].max) else 1
  160. # 轴系 叶尖速比
  161. tsr = "tsr"
  162. self.axisStepTSR = conf.dataContract.graphSets[tsr].step if not self.common.isNone(
  163. conf.dataContract.graphSets[tsr]) and not self.common.isNone(
  164. conf.dataContract.graphSets[tsr].step) else 5
  165. self.axisLowerLimitTSR = conf.dataContract.graphSets[tsr].min if not self.common.isNone(
  166. conf.dataContract.graphSets[tsr].min) else 0
  167. self.axisUpperLimitTSR = conf.dataContract.graphSets[tsr].max if not self.common.isNone(
  168. conf.dataContract.graphSets[tsr].max) else 20
  169. @abstractmethod
  170. def typeAnalyst(self):
  171. pass
  172. def getOutputAnalysisDir(self):
  173. """
  174. 获取当前分析的输出目录
  175. """
  176. outputAnalysisDir = f"output/{self.conf.dataContract.dataFilter.powerFarmID}/{self.conf.dataContract.dataFilter.dataBatchNum}/{self.typeAnalyst()}/{self.conf.dataContract.autoOrManual}"
  177. if not dir.check_directory_exists(outputAnalysisDir):
  178. dir.create_directory(outputAnalysisDir)
  179. return outputAnalysisDir
  180. def filterCommon(self, dataFrame: pd.DataFrame, conf: Contract):
  181. # Filter rows where turbine state
  182. if Field_StatusOfTurbine in dataFrame.columns and self.customFilterStatusOfTurbine in conf.dataContract.dataFilter.customFilter.keys() and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterStatusOfTurbine]):
  183. stateTurbine = conf.dataContract.dataFilter.customFilter[self.customFilterStatusOfTurbine]
  184. dataFrame = dataFrame[dataFrame[Field_StatusOfTurbine].isin(
  185. stateTurbine)]
  186. # Filter rows where wind speed
  187. if Field_WindSpeed in dataFrame.columns and self.customFilterWindSpeed in conf.dataContract.dataFilter.customFilter.keys() and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterWindSpeed]) and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterWindSpeed].min):
  188. windSpeedMin = conf.dataContract.dataFilter.customFilter[self.customFilterWindSpeed].min
  189. dataFrame = dataFrame[(
  190. dataFrame[Field_WindSpeed] >= windSpeedMin)]
  191. if Field_WindSpeed in dataFrame.columns and self.customFilterWindSpeed in conf.dataContract.dataFilter.customFilter.keys() and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterWindSpeed]) and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterWindSpeed].max):
  192. windSpeedMax = conf.dataContract.dataFilter.customFilter[self.customFilterWindSpeed].max
  193. dataFrame = dataFrame[(
  194. dataFrame[Field_WindSpeed] < windSpeedMax)]
  195. # Filter rows where pitch
  196. if Field_PitchAngel1 in dataFrame.columns and self.customFilterPitchAngle in conf.dataContract.dataFilter.customFilter.keys() and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterPitchAngle]) and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterPitchAngle].min):
  197. anglePitchMin = conf.dataContract.dataFilter.customFilter[
  198. self.customFilterPitchAngle].min
  199. dataFrame = dataFrame[(
  200. dataFrame[Field_PitchAngel1] >= anglePitchMin)]
  201. if Field_PitchAngel1 in dataFrame.columns and self.customFilterPitchAngle in conf.dataContract.dataFilter.customFilter.keys() and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterPitchAngle]) and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterPitchAngle].max):
  202. anglePitchMax = conf.dataContract.dataFilter.customFilter[
  203. self.customFilterPitchAngle].max
  204. dataFrame = dataFrame[(
  205. dataFrame[Field_PitchAngel1] < anglePitchMax)]
  206. # Filter rows where power
  207. if Field_ActiverPower in dataFrame.columns and self.customFilterActivePower in conf.dataContract.dataFilter.customFilter.keys() and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterActivePower]) and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterActivePower].min):
  208. activePowerMin = conf.dataContract.dataFilter.customFilter[
  209. self.customFilterActivePower].min
  210. dataFrame = dataFrame[(
  211. dataFrame[Field_ActiverPower] >= activePowerMin)]
  212. if Field_ActiverPower in dataFrame.columns and self.customFilterActivePower in conf.dataContract.dataFilter.customFilter.keys() and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterActivePower]) and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterActivePower].max):
  213. activePowerMax = conf.dataContract.dataFilter.customFilter[
  214. self.customFilterActivePower].max
  215. dataFrame = dataFrame[(
  216. dataFrame[Field_ActiverPower] < activePowerMax)]
  217. # Filter rows where generator speed
  218. if Field_GeneratorSpeed in dataFrame.columns and self.customFilterGeneratorSpeed in conf.dataContract.dataFilter.customFilter.keys() and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterGeneratorSpeed]) and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterGeneratorSpeed].min):
  219. speedGeneratorMin = conf.dataContract.dataFilter.customFilter[
  220. self.customFilterGeneratorSpeed].min
  221. dataFrame = dataFrame[(
  222. dataFrame[Field_GeneratorSpeed] >= speedGeneratorMin)]
  223. if Field_GeneratorSpeed in dataFrame.columns and self.customFilterGeneratorSpeed in conf.dataContract.dataFilter.customFilter.keys() and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterGeneratorSpeed]) and not self.common.isNone(conf.dataContract.dataFilter.customFilter[self.customFilterGeneratorSpeed].max):
  224. speedGeneratorMax = conf.dataContract.dataFilter.customFilter[
  225. self.customFilterGeneratorSpeed].max
  226. dataFrame = dataFrame[(
  227. dataFrame[Field_GeneratorSpeed] < speedGeneratorMax)]
  228. # if not self.common.isNone(confData.field_activePowerSet) and confData.field_activePowerSet in dataFrame.columns:
  229. # dataFrame = dataFrame[dataFrame[confData.field_activePowerSet]
  230. # == confData.rated_power]
  231. # if not self.common.isNone(confData.field_activePowerAvailable) and confData.field_activePowerAvailable in dataFrame.columns \
  232. # and self.node_activePowerAvailable in confData.filter and not self.common.isNone(confData.filter[self.node_activePowerAvailable]):
  233. # state = confData.filter[self.node_activePowerAvailable]
  234. # dataFrame = dataFrame[dataFrame[confData.field_activePowerAvailable].isin(
  235. # state)]
  236. return dataFrame
  237. def filterCustomForTurbine(self, dataFrame: pd.DataFrame, conf: Contract):
  238. return self.filterCommon(dataFrame, conf)
  239. def analysisOfTurbine(self,
  240. outputAnalysisDir,
  241. outputFilePath,
  242. conf: Contract,
  243. turbineCode):
  244. return self.turbineAnalysis(outputAnalysisDir,
  245. outputFilePath, conf, turbineCode)
  246. def turbineAnalysis(self,
  247. outputAnalysisDir,
  248. outputFilePath,
  249. conf: Contract,
  250. turbineCode):
  251. return pd.DataFrame()
  252. def filterCustomForTurbines(self, dataFrame: pd.DataFrame, conf: Contract):
  253. return self.filterCommon(dataFrame, conf)
  254. def analysisOfTurbines(self, outputAnalysisDir, conf: Contract, turbineCodes):
  255. # self.logger.info(
  256. # f"typeAnalyst: {self.typeAnalyst()} method: analysisOfTurbines , turbineCodes : {turbineCodes}")
  257. return self.turbinesAnalysis(outputAnalysisDir, conf, turbineCodes)
  258. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  259. return pd.DataFrame()
  260. def userDataFrame(self, dictionary: dict, configs: list[ConfigAnalysis], analyst) -> pd.DataFrame:
  261. timeGranularity = next(
  262. (config.scada for config in configs if config.className.lower() == type(analyst).__name__.lower()), None)
  263. return pd.DataFrame() if dictionary[timeGranularity].empty else dictionary[timeGranularity]
  264. def calculateAngleIncluded(self, array1, array2):
  265. """
  266. 计算两个相同长度角度数组中两两对应角度值的偏差。
  267. 结果限制在-90°到+90°之间,并保留两位小数。
  268. 参数:
  269. array1 (list): 第一个角度数组
  270. array2 (list): 第二个角度数组
  271. 返回:
  272. list: 两两对应角度的偏差列表
  273. """
  274. deviations = []
  275. for angle1, angle2 in zip(array1, array2):
  276. # 计算原始偏差
  277. deviation = angle1-angle2
  278. # 调整偏差,使其位于-180°到+180°范围内
  279. if deviation == 0.0:
  280. deviation = 0.0
  281. else:
  282. deviation = (deviation + 180) % 360 - 180
  283. # 将偏差限制在-90°到+90°范围内
  284. if deviation > 90:
  285. deviation -= 180
  286. elif deviation < -90:
  287. deviation += 180
  288. # 保留两位小数
  289. deviations.append(round(deviation, 2))
  290. return deviations
  291. def recalculationOfIncludedAngle(self, dataFrame: pd.DataFrame, fieldAngleIncluded, fieldWindDirect, fieldNacellePos):
  292. """
  293. 依据机舱位置(角度)、风向计算两者夹角
  294. """
  295. if fieldNacellePos in dataFrame.columns and fieldWindDirect in dataFrame.columns and not dataFrame[fieldNacellePos].isna().all() and not dataFrame[fieldWindDirect].isna().all():
  296. # 计算 angle_included_list
  297. angle_included_list = self.calculateAngleIncluded(
  298. dataFrame[fieldWindDirect], dataFrame[fieldNacellePos])
  299. # 检查 angle_included_list 的长度是否与 dataFrame 一致
  300. if len(angle_included_list) != len(dataFrame):
  301. raise ValueError(
  302. "The length of the calculated angle_included_list does not match the length of the DataFrame")
  303. # 创建一个新的列来保存计算结果
  304. dataFrame['Calculated_AngleIncluded'] = angle_included_list
  305. # 使用新列填充原列的缺失值
  306. dataFrame[Field_AngleIncluded] = dataFrame[Field_AngleIncluded].fillna(
  307. dataFrame['Calculated_AngleIncluded'])
  308. # 删除临时列
  309. dataFrame.drop(columns=['Calculated_AngleIncluded'], inplace=True)
  310. def recalculationOfFieldPowerFloor(self, dataFrame: pd.DataFrame, fieldActivePower):
  311. """
  312. 功率计算
  313. """
  314. if fieldActivePower in dataFrame.columns:
  315. dataFrame[Field_PowerFloor] = dataFrame[fieldActivePower].apply(
  316. lambda x: int(
  317. x / 10) * 10 if pd.notnull(x) or pd.notna(x) else np.nan # 保留NaN值
  318. )
  319. def recalculationOfFieldWindSpeedFloor(self, dataFrame: pd.DataFrame, fieldWindSpeed):
  320. """
  321. 风速计算
  322. """
  323. if fieldWindSpeed in dataFrame.columns:
  324. dataFrame[Field_WindSpeedFloor] = dataFrame[fieldWindSpeed].apply(
  325. lambda x: int(x/1) +
  326. 0.5 if pd.notnull(x) or pd.notna(x) else np.nan
  327. )
  328. def recalculationOfGeneratorSpeed(self, turbineModelInfo: pd.Series, dataFrame: pd.DataFrame, fieldRotorSpeed, fieldGeneratorSpeed, rotationalSpeedRatio: float):
  329. """
  330. 风电机组发电机转速再计算,公式:转速比=发电机转速/叶轮或主轴转速
  331. """
  332. if fieldGeneratorSpeed in dataFrame.columns and fieldRotorSpeed in dataFrame.columns:
  333. if turbineModelInfo[Field_MotionType] == 2:
  334. dataFrame[fieldGeneratorSpeed] = dataFrame[fieldGeneratorSpeed].fillna(
  335. dataFrame[fieldRotorSpeed])
  336. # dataFrame[fieldGeneratorSpeed] =dataFrame[fieldRotorSpeed]
  337. else:
  338. dataFrame[fieldGeneratorSpeed] = dataFrame[fieldGeneratorSpeed].fillna(
  339. dataFrame[fieldRotorSpeed]*rotationalSpeedRatio)
  340. # dataFrame[fieldGeneratorSpeed] = dataFrame[fieldRotorSpeed]*rotationalSpeedRatio
  341. def recalculationOfRotorSpeed(self, turbineModelInfo: pd.Series, dataFrame: pd.DataFrame, fieldRotorSpeed, fieldGeneratorSpeed, rotationalSpeedRatio: float):
  342. """
  343. 风电机组发电机转速再计算,公式:转速比=发电机转速/叶轮或主轴转速
  344. """
  345. if fieldRotorSpeed in dataFrame.columns and fieldGeneratorSpeed in dataFrame.columns:
  346. if turbineModelInfo[Field_MotionType] == 2:
  347. dataFrame[fieldRotorSpeed] = dataFrame[fieldRotorSpeed].fillna(
  348. dataFrame[fieldGeneratorSpeed])
  349. else:
  350. dataFrame[fieldRotorSpeed] = dataFrame[fieldRotorSpeed].fillna(
  351. dataFrame[fieldGeneratorSpeed] / rotationalSpeedRatio)
  352. def recalculationOfRotorTorque(self, dataFrame: pd.DataFrame, fieldGeneratorTorque, fieldActivePower, fieldGeneratorSpeed):
  353. """
  354. 风电机组发电机转矩计算,P的单位换成KW转矩计算公式:
  355. P*1000= pi/30*T*n
  356. 30000/pi*P=T*n
  357. 30000/3.1415926*P=T*n
  358. 9549.297*p=T*n
  359. 其中:n为发电机转速,p为有功功率,T为转矩
  360. """
  361. if fieldGeneratorTorque in dataFrame.columns and fieldActivePower in dataFrame.columns and fieldGeneratorSpeed in dataFrame.columns:
  362. dataFrame[Field_GeneratorTorque] = dataFrame[Field_GeneratorTorque].fillna(
  363. 9549.297 * dataFrame[fieldActivePower]/dataFrame[fieldGeneratorSpeed])
  364. def recalculation(self, turbineModelInfo: pd.Series, dataFrame: pd.DataFrame):
  365. """
  366. 再计算数据测点
  367. 参数:
  368. dataFrame 原始数据
  369. conf 配置数据
  370. """
  371. self.recalculationOfFieldPowerFloor(dataFrame, Field_ActiverPower)
  372. self.recalculationOfFieldWindSpeedFloor(dataFrame, Field_WindSpeed)
  373. self.recalculationOfGeneratorSpeed(
  374. turbineModelInfo, dataFrame, Field_RotorSpeed, Field_GeneratorSpeed, self.turbineModelInfo[Field_RSR].iloc[0])
  375. self.recalculationOfRotorSpeed(turbineModelInfo, dataFrame, Field_RotorSpeed,
  376. Field_GeneratorSpeed, self.turbineModelInfo[Field_RSR].iloc[0])
  377. self.recalculationOfRotorTorque(
  378. dataFrame, Field_GeneratorTorque, Field_ActiverPower, Field_GeneratorSpeed)
  379. self.recalculationOfIncludedAngle(
  380. dataFrame, Field_AngleIncluded, Field_WindDirection, Field_NacPos)
  381. self.common.calculateTSR(
  382. dataFrame, self.turbineModelInfo[Field_RotorDiameter].iloc[0])
  383. self.common.calculateCpOfSingleTurbine(
  384. dataFrame, self.currPowerFarmInfo[Field_AirDensity], turbineModelInfo[Field_RotorDiameter])
  385. def processDateTimeForAll(self, dataFrame: pd.DataFrame):
  386. dataFrame[Field_UnixYearMonth] = pd.to_datetime(
  387. dataFrame[Field_Time], format="%Y-%m").apply(lambda x: x.timestamp())
  388. dataFrame[Field_YearMonth] = dataFrame[Field_Time].dt.strftime('%Y-%m')
  389. dataFrame[Field_YearMonthDay] = dataFrame[Field_Time].dt.strftime(
  390. '%Y-%m-%d')
  391. return dataFrame
  392. def processDateTime(self, dataFrame: pd.DataFrame, fieldTime: str = None):
  393. if Field_Time not in dataFrame.columns:
  394. self.logger.info(
  395. "Field_Time is not in dataFrame columns, Do not handle time processing")
  396. return dataFrame
  397. if fieldTime is None:
  398. return self.processDateTimeForAll(dataFrame)
  399. timeSeries = pd.Series
  400. if fieldTime == Field_UnixYearMonth:
  401. timeSeries = pd.to_datetime(
  402. dataFrame[Field_Time], format="%Y-%m").apply(lambda x: x.timestamp())
  403. if fieldTime == Field_YearMonth:
  404. timeSeries = dataFrame[Field_Time].dt.strftime('%Y-%m')
  405. if fieldTime == Field_YearMonthDay:
  406. timeSeries = dataFrame[Field_Time].dt.strftime('%Y-%m-%d')
  407. dataFrame[fieldTime] = timeSeries
  408. return dataFrame
  409. def loadData(self, powerFarmID: str, timeGranularity: str, turbineCode: str, select: str, condition: str) -> pd.DataFrame:
  410. selectStr = ", ".join(f"{field}" for field in select)
  411. businessDB: DatabaseUtil = GetBusinessDbUtil()
  412. # query_text = f"""SELECT wind_turbine_number,time_stamp, active_power,wind_velocity,cabin_position,true_wind_direction, rotor_speed, generator_speed,actual_torque,yaw_error1,outside_cabin_temperature,cabin_temperature,main_bearing_temperature,gearboxmedium_speed_shaftbearing_temperature,gearbox_low_speed_shaft_bearing_temperature,gearbox_high_speed_shaft_bearing_temperature,generatordrive_end_bearing_temperature,generatornon_drive_end_bearing_temperature,generator_winding1_temperature,pitch_angle_blade_1,pitch_angle_blade_2,pitch_angle_blade_3,front_back_vibration_of_the_cabin,side_to_side_vibration_of_the_cabin
  413. # FROM `{dataBatchNum}_{timeGranularity}`
  414. # WHERE wind_turbine_number IN ({turbineCode}) AND {condition}"""
  415. query_text = f"""SELECT {selectStr}
  416. FROM `{powerFarmID}_{timeGranularity}`
  417. WHERE wind_turbine_number IN ({turbineCode}) AND {condition}"""
  418. # select = [Field_DeviceCode, Field_Time, Field_ActiverPower, Field_WindSpeed, Field_NacPos, Field_WindDirection, Field_RotorSpeed, Field_GeneratorSpeed, Field_GeneratorTorque, Field_AngleIncluded, Field_EnvTemp, Field_NacTemp, Field_MainBearTemp, Field_GbMsBearTemp, Field_GbLsBearTemp,
  419. # Field_GbHsBearTemp, Field_GeneratorDE, Field_GeneratorNDE, Field_GenWiTemp1, Field_PitchAngel1, Field_PitchAngel2, Field_PitchAngel3, Field_NacFbVib, Field_NacLrVib]
  420. chunks = []
  421. with businessDB.session_scope() as session:
  422. # query_result = session.execute(text(query_text)).fetchall()
  423. # dataFrame = pd.DataFrame(query_result, columns=select)
  424. # return dataFrame
  425. conn=session.connection()
  426. result=conn.execution_options(stream_results=True).execute(text(query_text))
  427. # 每次取 50000 行,可根据实际情况调整
  428. while True:
  429. rows = result.fetchmany(50000)
  430. if not rows:
  431. break
  432. chunks.append(pd.DataFrame(rows, columns=select))
  433. if not chunks:
  434. return pd.DataFrame()
  435. return pd.concat(chunks, ignore_index=True)
  436. def dataProcess(self, powerFarmID: str, dataBatchNum: str, timeGranularity: str, turbineCode: str, select: str, customCondition) -> Tuple[pd.DataFrame, str, str]:
  437. self.logger.info(
  438. f"load data -> Time Granulary: {timeGranularity} Power Farm: {powerFarmID} Batch: {dataBatchNum} Turbine: {turbineCode} .")
  439. # Get current turbine info
  440. currTurbineInfo = self.common.getTurbineInfo(
  441. self.conf.dataContract.dataFilter.powerFarmID, turbineCode, self.turbineInfo)
  442. # Get turbine model info
  443. currTurbineModelInfo = self.common.getTurbineModelByTurbine(
  444. currTurbineInfo, self.turbineModelInfo)
  445. # select DB data
  446. dataFrameOfTurbine = self.loadData(
  447. powerFarmID, timeGranularity, f"'{turbineCode}'", select, customCondition)
  448. if dataFrameOfTurbine.empty:
  449. self.logger.warning(
  450. f"{turbineCode} Time Granulary: {timeGranularity} scada data is empty.")
  451. return pd.DataFrame(), dataBatchNum, turbineCode
  452. else:
  453. if timeGranularity in ['fault', 'warn']:
  454. return dataFrameOfTurbine, dataBatchNum, turbineCode
  455. else:
  456. # Add property to dataFrame
  457. self.addPropertyToDataFrame(
  458. dataFrameOfTurbine, currTurbineInfo, currTurbineModelInfo)
  459. # Add engine_name to dataFrame
  460. dataFrameOfTurbine[Field_NameOfTurbine] = currTurbineInfo.loc[Field_NameOfTurbine]
  461. # Additional data processing steps
  462. self.processDateTime(dataFrameOfTurbine)
  463. # Recalculation
  464. self.recalculation(currTurbineModelInfo, dataFrameOfTurbine)
  465. # Filter data
  466. dataFrameOfTurbine = self.filterCommon(
  467. dataFrameOfTurbine, self.conf)
  468. return dataFrameOfTurbine, dataBatchNum, turbineCode
  469. def selectTimeCondition(self, conf: Contract, conditions: list[str]):
  470. """
  471. 时间过滤条件组装
  472. 从json配置中获取时间过滤条件进行组装
  473. """
  474. # 时间过滤条件
  475. if conf.dataContract.dataFilter.beginTime:
  476. conditions.append(
  477. f"time_stamp >= '{conf.dataContract.dataFilter.beginTime}'")
  478. if conf.dataContract.dataFilter.endTime:
  479. conditions.append(
  480. f"time_stamp <= '{conf.dataContract.dataFilter.endTime}'")
  481. # 排除月份
  482. if conf.dataContract.dataFilter.excludingMonths:
  483. excluding_months = ", ".join(
  484. f"'{month}'" for month in conf.dataContract.dataFilter.excludingMonths)
  485. excluding_condition = f"DATE_FORMAT(time_stamp, '%Y-%m') NOT IN ({excluding_months})"
  486. conditions.append(excluding_condition)
  487. def selectLabCondition(self, conditions: list[str]):
  488. """
  489. lab 过滤条件组装
  490. 根据lab获取相应的数据
  491. 逻辑实现在相应的Behavior子类中,子类没有实现则无需过滤
  492. """
  493. return
  494. def selectAllCondition(self, conf: Contract):
  495. conditions = []
  496. # 时间过滤条件
  497. self.selectTimeCondition(conf, conditions)
  498. # lab过滤
  499. self.selectLabCondition(conditions)
  500. return " AND ".join(conditions) if conditions else "1=1"
  501. def selectFaultTimeCondition(self, conf: Contract, conditions: list[str]):
  502. """
  503. 时间过滤条件组装
  504. 从json配置中获取时间过滤条件进行组装
  505. """
  506. # 时间过滤条件
  507. if conf.dataContract.dataFilter.beginTime:
  508. conditions.append(
  509. f"begin_time >= '{conf.dataContract.dataFilter.beginTime}'")
  510. if conf.dataContract.dataFilter.endTime:
  511. conditions.append(
  512. f"end_time <= '{conf.dataContract.dataFilter.endTime}'")
  513. # 排除月份
  514. if conf.dataContract.dataFilter.excludingMonths:
  515. excluding_months = ", ".join(
  516. f"'{month}'" for month in conf.dataContract.dataFilter.excludingMonths)
  517. excluding_condition = f"DATE_FORMAT(time_stamp, '%Y-%m') NOT IN ({excluding_months})"
  518. conditions.append(excluding_condition)
  519. # 故障数据过滤条件
  520. def selectAllFaultCondition(self, conf: Contract):
  521. conditions = []
  522. # 时间过滤条件
  523. self.selectFaultTimeCondition(conf, conditions)
  524. # lab过滤
  525. self.selectLabCondition(conditions)
  526. return " AND ".join(conditions) if conditions else "1=1"
  527. def processTurbineData(self, turbines, conf: Contract, select: str):
  528. try:
  529. # add "where" condition
  530. select_conditions = self.selectAllCondition(conf)
  531. scadaTimeGranularities = self.getTimeGranularitys(conf)
  532. self.logger.info(
  533. f"typeAnalyst: {self.typeAnalyst()} method: processTurbineData , scadaTimeGranularities : {scadaTimeGranularities} current class : {self.__class__.__name__}" )
  534. dictionary = dict()
  535. for timeGranularity in scadaTimeGranularities:
  536. dataFrames = []
  537. dataFrameOfTurbines = pd.DataFrame()
  538. if timeGranularity in ['fault', 'warn']:
  539. select_conditions = self.selectAllFaultCondition(conf)
  540. maxWorkers = 3
  541. with concurrent.futures.ThreadPoolExecutor(max_workers=maxWorkers) as executor:
  542. futures = [
  543. executor.submit(self.dataProcess, conf.dataContract.dataFilter.powerFarmID,
  544. conf.dataContract.dataFilter.dataBatchNum, timeGranularity, turbine, select, select_conditions)
  545. for turbine in turbines
  546. ]
  547. for future in concurrent.futures.as_completed(futures):
  548. try:
  549. dataFrameOfTurbine, dataBatchNum, turbineCode = future.result()
  550. dataFrames.append(dataFrameOfTurbine)
  551. self.logger.info(
  552. f"typeAnalyst: {self.typeAnalyst()} data frame append,dataBatchNum: {dataBatchNum} timeGranularity: {timeGranularity} turbineCode: {turbineCode}")
  553. except Exception as exc:
  554. raise exc
  555. dataFrameOfTurbines = pd.concat(dataFrames, ignore_index=True)
  556. self.logger.info(
  557. f"typeAnalyst: {self.typeAnalyst()} data frame concat dataBatchNum: {dataBatchNum} timeGranularity: {timeGranularity} finish")
  558. if dataFrameOfTurbines.empty:
  559. # excption = CustomError(102)
  560. # self.logger.warning(
  561. # f"{excption.message} typeAnalyst: {self.typeAnalyst()} Power Farm: {conf.dataContract.dataFilter.powerFarmID} Batch : {conf.dataContract.dataFilter.dataBatchNum} Time Granularity : {timeGranularity}")
  562. # raise excption
  563. # 改为仅记录警告,不抛出异常
  564. self.logger.warning(
  565. f"Data is empty after processing. typeAnalyst: {self.typeAnalyst()} Power Farm: {conf.dataContract.dataFilter.powerFarmID} Batch : {conf.dataContract.dataFilter.dataBatchNum} Time Granularity : {timeGranularity}")
  566. dictionary[timeGranularity] = pd.DataFrame()
  567. else:
  568. self.logger.info(
  569. f"typeAnalyst: {self.typeAnalyst()} data frame concat dataBatchNum: {dataBatchNum} timeGranularity: {timeGranularity} dataFrameOfTurbines : {dataFrameOfTurbines}")
  570. if Field_DeviceCode in dataFrameOfTurbines.columns and Field_CodeOfTurbine not in dataFrameOfTurbines.columns:
  571. dataFrameOfTurbines = dataFrameOfTurbines.rename(
  572. columns={Field_DeviceCode: Field_CodeOfTurbine})
  573. dictionary[timeGranularity] = dataFrameOfTurbines
  574. return dictionary
  575. except Exception as e:
  576. self.logger.error(
  577. f"Error processing turbine data:{traceback.format_exc()}")
  578. raise
  579. def addPropertyToDataFrame(self, dataFrameOfTurbine: pd.DataFrame, currTurbineInfo: pd.Series, currTurbineModelInfo: pd.Series):
  580. """
  581. 用来添加额外当前风机属性
  582. 在business中相应的分析员中有实现,没有实现就无额外参数添加
  583. """
  584. return
  585. def getTimeGranularitys(self, conf: str):
  586. """
  587. 根据配置获取数据源类型
  588. """
  589. configAnalysisDF = pd.DataFrame(
  590. [config.to_dict() for config in conf.dataContract.configAnalysis])
  591. configAnalysisDF = configAnalysisDF[(configAnalysisDF["className"] == self.__class__.__name__)]
  592. return configAnalysisDF["scada"].unique()
  593. def escape_special_characters(self, original_string: str):
  594. """
  595. ---废弃---
  596. 特殊字符url编码处理
  597. "/" 符号单独处理
  598. """
  599. retrun_string = quote(original_string)
  600. if "/" in retrun_string:
  601. retrun_string = retrun_string.replace("/", "%2F")
  602. return retrun_string