2 İşlemeler 3901604c6e ... 4601574a3a

Yazar SHA1 Mesaj Tarih
  wangjiaojiao 4601574a3a 中广核消缺:大部件温差回溯至温升版本 2 ay önce
  wangjiaojiao f19b7568ac 中广核消缺:故障分析功能逻辑修改 2 ay önce

+ 77 - 74
dataAnalysisBusiness/algorithm/faultAnalyst.py

@@ -1,121 +1,124 @@
 import pandas as pd
 import os
-import plotly.graph_objects as go
 from algorithmContract.confBusiness import *
 from algorithmContract.contract import Contract
 from behavior.analystNotFilter import AnalystNotFilter
-from plotly.subplots import make_subplots
 
 class FaultAnalyst(AnalystNotFilter):
     """
     风电机组故障分析
+    新逻辑:基于首发故障结束时间划分窗口, 以首发故障的结束时间为界,后续故障的开始时间如果 ≤ 这个结束时间,则视为连带故障;否则作为新的首发故障。
+
+    风场范围的故障分析
+    统计风场范围内 首发故障中各故障发生的次数和累计时长
     """
 
     def typeAnalyst(self):
         return "fault"
-    
-    def selectColumns(self):
-        # 这里的字段必须与数据库 _fault 表中的列名对应
-        return [Field_DeviceName, Field_FaultTime, Field_FaultDetail]
 
-    # 强制重写获取数据源类型的方法,防止因配置错误去查 _minute 表,强制代码去查 _fault 表
+    def selectColumns(self):
+        # 直接返回数据库表中的真实列名
+        return ["wind_turbine_name", "begin_time", "end_time", "fault_detail"]
+        #风机原始名称、故障开始时间、故障结束时间、故障描述
     def getTimeGranularitys(self, conf: Contract):
-        return ["fault"] 
+        return ["fault"]
 
     def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
-        # 1. 抓取数据
         dictionary = self.processTurbineData(turbineCodes, conf, self.selectColumns())
-        
-        # 2. 直接获取 "fault" key 的数据, 绕过父类的 userDataFrame 方法,因为它会读取错误的配置文件 ('minute')
         dataFrameMerge = dictionary.get("fault", pd.DataFrame())
-        
-        # 3. 增加空数据保护
         if dataFrameMerge.empty:
-            # print("Warning: No fault data found for the selected turbines.")
             return pd.DataFrame()
-            
         return self.get_result(dataFrameMerge, outputAnalysisDir, conf)
 
     def get_result(self, dataFrame: pd.DataFrame, outputAnalysisDir: str, conf: Contract):
-        # 双重保险:如果数据为空直接返回
         if dataFrame.empty:
             return pd.DataFrame()
 
-        #---------------整个风场维度统计故障时长与次数---------------------------
-        # 统计各种类型故障出现的次数
-        if Field_FaultDetail in dataFrame.columns:
-            fault_detail_count = dataFrame[Field_FaultDetail].value_counts().reset_index()
-            fault_detail_count.columns = [Field_FaultDetail, 'count']
+        df = dataFrame.copy()
+        
+        # 转换时间列
+        df["begin_time"] = pd.to_datetime(df["begin_time"])
+        df["end_time"] = pd.to_datetime(df["end_time"])
+        df = df.dropna(subset=["begin_time", "end_time"])
 
-            # 统计每个 fault_detail 的时长加和
-            fault_time_sum = dataFrame.groupby(Field_FaultDetail)[Field_FaultTime].sum().reset_index()
-            fault_time_sum.columns = [Field_FaultDetail, 'fault_time_sum']
+        if df.empty:
+            return pd.DataFrame()
 
-            # 合并两个 DataFrame
-            fault_summary = pd.merge(fault_detail_count, fault_time_sum, on=Field_FaultDetail, how='inner')
-            fault_summary_sorted = fault_summary.sort_values(by='fault_time_sum', ascending=False)
+        # 存储中间结果
+        turbine_events = []
+        fault_events = []
+
+        # 按风机分组处理
+        for turbine, group in df.groupby("wind_turbine_name"):
+            group = group.sort_values("begin_time").reset_index(drop=True)
+            i = 0
+            n = len(group)
+            while i < n:
+                primary = group.iloc[i]
+                primary_start = primary["begin_time"]
+                primary_end = primary["end_time"]
+                duration_sec = (primary_end - primary_start).total_seconds()
+
+                turbine_events.append({
+                    "turbine": turbine,
+                    "duration_sec": duration_sec
+                })
+                fault_events.append({
+                    "fault_detail": primary["fault_detail"],
+                    "duration_sec": duration_sec
+                })
+
+                # 跳过连带故障
+                j = i + 1
+                while j < n and group.iloc[j]["begin_time"] <= primary_end:
+                    j += 1
+                i = j
+
+        # 聚合风机维度
+        if turbine_events:
+            turbine_df = pd.DataFrame(turbine_events)
+            turbine_summary = turbine_df.groupby("turbine").agg(
+                count=("duration_sec", "size"),
+                fault_time=("duration_sec", "sum")
+            ).reset_index()
+            turbine_summary = turbine_summary.rename(columns={"turbine": "wind_turbine_name"})
+            turbine_file = os.path.join(outputAnalysisDir, f"turbine_fault_result{CSVSuffix}")
+            turbine_summary.to_csv(turbine_file, index=False, encoding='utf-8-sig')
         else:
-            # 防御性代码:如果缺列
-            fault_summary_sorted = pd.DataFrame()
-
-        # -------------按风机分组统计故障情况------------------------------------------
-        # 确保有设备名称列
-        if Field_DeviceName not in dataFrame.columns:
-             # 有时 Field_DeviceName 没取到,尝试用 DeviceCode 或其他
-             groupby_col = dataFrame.columns[0] 
+            turbine_summary = pd.DataFrame()
+
+        # 聚合故障类型维度
+        if fault_events:
+            fault_df = pd.DataFrame(fault_events)
+            fault_summary = fault_df.groupby("fault_detail").agg(
+                count=("duration_sec", "size"),
+                fault_time_sum=("duration_sec", "sum")
+            ).reset_index()
+            fault_file = os.path.join(outputAnalysisDir, f"total_fault_result{CSVSuffix}")
+            fault_summary.to_csv(fault_file, index=False, encoding='utf-8-sig')
         else:
-             groupby_col = Field_DeviceName
-
-        grouped = dataFrame.groupby(groupby_col)
-        results= []
+            fault_summary = pd.DataFrame()
 
-        for name, group in grouped:
-            turbine_fault_summary = pd.DataFrame({
-                Field_DeviceName: [name],
-                'count': [len(group)],
-                'fault_time': [group[Field_FaultTime].sum()]
-            })
-            results.append(turbine_fault_summary)
-
-        # 合并所有风机的故障统计结果
-        if results:
-            turbine_fault_summary = pd.concat(results, ignore_index=True)
-            turbine_fault_sorted = turbine_fault_summary.sort_values(by='fault_time', ascending=False)
-            # 故障类型前十名
-            # draw_results=turbine_fault_sorted.head(10) # 暂时没用到
-        else:
-            turbine_fault_sorted = pd.DataFrame()
-
-        # 保存结果
+        # 返回结果
         result_rows = []
-
-        if not turbine_fault_sorted.empty:
-            filePathOfturbinefault = os.path.join(outputAnalysisDir, f"turbine_fault_result{CSVSuffix}")
-            turbine_fault_sorted.to_csv(filePathOfturbinefault, index=False,encoding='utf-8-sig')
-
+        if not turbine_summary.empty:
             result_rows.append({
                 Field_Return_TypeAnalyst: self.typeAnalyst(),
                 Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
                 Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
                 Field_CodeOfTurbine: "total",
-                Field_MillTypeCode:"turbine_fault_result",
-                Field_Return_FilePath: filePathOfturbinefault,
+                Field_MillTypeCode: "turbine_fault_result",
+                Field_Return_FilePath: turbine_file,
                 Field_Return_IsSaveDatabase: True
             })
-
-        if not fault_summary_sorted.empty:
-            filePathOftotalfault = os.path.join(outputAnalysisDir, f"total_fault_result{CSVSuffix}")
-            fault_summary_sorted.to_csv(filePathOftotalfault, index=False,encoding='utf-8-sig')
-
+        if not fault_summary.empty:
             result_rows.append({
                 Field_Return_TypeAnalyst: self.typeAnalyst(),
                 Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
                 Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
                 Field_CodeOfTurbine: "total",
-                Field_MillTypeCode:"total_fault_result",
-                Field_Return_FilePath: filePathOftotalfault,
+                Field_MillTypeCode: "total_fault_result",
+                Field_Return_FilePath: fault_file,
                 Field_Return_IsSaveDatabase: True
             })
-            
-        result_df = pd.DataFrame(result_rows)
-        return result_df
+        return pd.DataFrame(result_rows)

+ 10 - 43
dataAnalysisBusiness/algorithm/temperatureLargeComponentsAnalyst.py

@@ -127,7 +127,7 @@ class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
         """
         大部件温度传感器分析
         """
-        y_name = '温(℃)'
+        y_name = '温(℃)'
 
         outputDir = os.path.join(outputAnalysisDir, "GeneratorTemperature")
         dir.create_directory(outputDir)
@@ -139,15 +139,7 @@ class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
         for column in temperatureCols:
             if not column in dataFrameMerge.columns:
                 continue
-            
-            # 判断是否是环境温度本身
-            is_env_temp = (column == Field_EnvTemp)
-            
             columnZH = TemperatureColumns.get(column)
-
-            if not is_env_temp:
-                columnZH = f"{columnZH}温差"
-
             outputPath = os.path.join(outputAnalysisDir, column)
             dir.create_directory(outputPath)
 
@@ -163,16 +155,6 @@ class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
             for idx, (name, group) in enumerate(grouped):
                 currTurbineInfo_group = self.common.getTurbineInfo(
                     conf.dataContract.dataFilter.powerFarmID, name, self.turbineInfo)
-                
-                # --- 数据计算逻辑 ---
-                x_data = group[Field_PowerFloor]
-                y_data = group[column]
-                
-                # 如果不是环境温度本身,且存在环境温度数据,则计算温差(部件温度 - 环境温度)
-                if not is_env_temp and Field_EnvTemp in group.columns:
-                    y_data = y_data - group[Field_EnvTemp]
-                # ------------------
-                
                 fig.add_trace(go.Scatter(
                     x=group[Field_PowerFloor],
                     y=group[column],
@@ -186,7 +168,7 @@ class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
                     "engineName": currTurbineInfo_group[Field_NameOfTurbine],
                     "engineCode": name,
                     "xData": group[Field_PowerFloor].tolist(),
-                    "yData": y_data.tolist(),
+                    "yData": group[column].tolist(),
                     "color": colors[idx % len(colors)]
                 }
                 turbine_data_list.append(turbine_data_total)
@@ -295,17 +277,9 @@ class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
                     if other_name != name:
                         tempTurbineInfo = self.common.getTurbineInfo(
                             conf.dataContract.dataFilter.powerFarmID, other_name, self.turbineInfo)
-                        
-                        # --- 数据计算 ---
-                        ox_data = other_group[Field_PowerFloor]
-                        oy_data = other_group[column]
-                        if not is_env_temp and Field_EnvTemp in other_group.columns:
-                            oy_data = oy_data - other_group[Field_EnvTemp]
-                        # ---------------
-                        
                         single_fig.add_trace(go.Scatter(
-                            x=ox_data,
-                            y=oy_data,
+                            x=other_group[Field_PowerFloor],
+                            y=other_group[column],
                             mode='lines',
                             name=tempTurbineInfo[Field_NameOfTurbine],
                             line=dict(color='lightgrey', width=1),
@@ -316,21 +290,14 @@ class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
                             "engineName": tempTurbineInfo[Field_NameOfTurbine],
                             "engineCode": other_name,
                             "xData": other_group[Field_PowerFloor].tolist(),
-                            "yData": oy_data.tolist(),
+                            "yData": other_group[column].tolist(),
                         }
                         turbine_data_list_each.append(turbine_data_other_each)
 
                 # Add the turbine of interest in dark blue
-                # --- 数据计算 ---
-                gx_data = group[Field_PowerFloor]
-                gy_data = group[column]
-                if not is_env_temp and Field_EnvTemp in group.columns:
-                    gy_data = gy_data - group[Field_EnvTemp]
-                # ---------------
-
                 single_fig.add_trace(go.Scatter(
-                    x=gx_data,
-                    y=gy_data,
+                    x=group[Field_PowerFloor],
+                    y=group[column],
                     mode='lines',
                     # Make it slightly thicker for visibility
                     line=dict(color='darkblue', width=2),
@@ -341,7 +308,7 @@ class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
                     "engineName": currTurbineInfo_each[Field_NameOfTurbine],
                     "engineCode": currTurbineInfo_each[Field_CodeOfTurbine],
                     "xData": group[Field_PowerFloor].tolist(),
-                    "yData": gy_data.tolist(),
+                    "yData": group[column].tolist(),
                 }
                 turbine_data_list_each.append(turbine_data_curr)
 
@@ -426,8 +393,8 @@ class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
             if not Field_GeneratorDE in group.columns or not Field_GeneratorNDE in group.columns or group[Field_GeneratorDE].isna().all()  or group[Field_GeneratorNDE].isna().all():
                 self.logger.warning(f"{name} 不具备发电机温度测点,或测点值全为空")
                 continue
-            refFieldTemperature = Field_NacTemp if group[Field_EnvTemp].isna(
-            ).all() else Field_EnvTemp
+            refFieldTemperature = Field_EnvTemp if group[Field_NacTemp].isna(
+            ).all() else Field_NacTemp
             result_rows1 = self.drawGeneratorTemperature(
                 group, conf, Field_GeneratorDE, Field_GeneratorNDE, refFieldTemperature, Field_PowerFloor, name, outputDir)
             result_rows.extend(result_rows1)