Xmia 5 giorni fa
parent
commit
d04ebb881f
1 ha cambiato i file con 597 aggiunte e 597 eliminazioni
  1. 597 597
      dataAnalysisBusiness/algorithm/temperatureLargeComponentsAnalyst.py

+ 597 - 597
dataAnalysisBusiness/algorithm/temperatureLargeComponentsAnalyst.py

@@ -1,597 +1,597 @@
-import os
-
-import pandas as pd
-import plotly.express as px
-import plotly.graph_objects as go
-from algorithmContract.confBusiness import *
-from algorithmContract.contract import Contract
-from behavior.analystWithGoodBadLimitPoint import AnalystWithGoodBadLimitPoint
-from plotly.subplots import make_subplots
-from utils.directoryUtil import DirectoryUtil as dir
-
-
-class Generator:
-    def __init__(self) -> None:
-        self.fieldTemperatorOfDEBearing = None
-        self.fieldTemperatorOfNDEBearing = None
-
-
-TemperatureColumns = {Field_MainBearTemp: "主轴承温度",
-                     Field_GbMsBearTemp: "齿轮箱中速轴温度",
-                      Field_GbLsBearTemp: "齿轮箱低速轴温度",
-                      Field_GbHsBearTemp: "齿轮箱高速轴温度",
-                      Field_GeneratorDE: "发电机驱动端轴承温度",
-                      Field_GeneratorNDE: "发电机非驱动端轴承温度",
-                      Field_GenWiTemp1: "发电机绕组温度"}
-
-GeneratorTemperatureAnslysisColumns = [
-    Field_GeneratorDE, Field_GeneratorNDE, Field_NacTemp]
-
-
-class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
-    """
-    风电机组大部件温升分析
-    """
-
-    def typeAnalyst(self):
-        return "temperature_large_components"
-
-    def getUseColumns(self, dataFrame: pd.DataFrame, temperatureColumns: list[dict]):
-        # 获取非全为空的列名
-        non_empty_cols = self.getNoneEmptyFields(dataFrame, temperatureColumns)
-
-        useCols = []
-        # useCols.append(Field_Time)
-        useCols.append(Field_ActiverPower)
-
-        if not self.common.isNone(Field_EnvTemp) and Field_EnvTemp in dataFrame.columns:
-            useCols.append(Field_EnvTemp)
-
-        if not self.common.isNone(Field_NacTemp) and Field_NacTemp in dataFrame.columns:
-            useCols.append(Field_NacTemp)
-
-        useCols.extend(non_empty_cols)
-
-        return useCols
-
-    def getNoneEmptyFields(self, dataFrame: pd.DataFrame, temperatureColumns: dict) -> list:
-        # 使用set和列表推导式来获取在DataFrame中存在的字段
-        existing_fields = [
-            key for key in TemperatureColumns.keys() if key in dataFrame.columns
-        ]
-        # 检查指定列中非全为空的列
-        non_empty_columns = dataFrame[existing_fields].apply(
-            lambda x: x.notnull().any(), axis=0)
-        # 获取非全为空的列名
-        noneEmptyFields = non_empty_columns[non_empty_columns].index.tolist()
-        return noneEmptyFields
-
-    def dataReprocess(self, dataFrame: pd.DataFrame,  non_empty_cols: list):
-        # Initialize an empty df for aggregation
-        agg_dict = {col: 'median' for col in non_empty_cols}
-
-        # Group by 'power_floor' and aggregate
-        grouped = dataFrame.groupby([Field_PowerFloor, Field_CodeOfTurbine, Field_NameOfTurbine]).agg(agg_dict).reset_index()
-
-        # Sort by 'power_floor'
-        grouped.sort_values(
-            [Field_PowerFloor, Field_CodeOfTurbine, Field_NameOfTurbine], inplace=True)
-
-        return grouped
-
-    def turbinesAnalysis(self,  outputAnalysisDir, conf: Contract, turbineCodes):
-        select=[Field_DeviceCode, Field_Time, Field_WindSpeed, Field_ActiverPower]
-        select=select+[Field_EnvTemp,Field_NacTemp]+list(TemperatureColumns.keys())
-
-        dictionary = self.processTurbineData(turbineCodes, conf,select )
-        dataFrameOfTurbines = self.userDataFrame(
-            dictionary, conf.dataContract.configAnalysis, self)
-
-        turbrineInfos = self.common.getTurbineInfos(
-            conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
-
-        groupedOfTurbineModel = turbrineInfos.groupby(Field_MillTypeCode)
-
-        returnDatas = []
-        for turbineModelCode, group in groupedOfTurbineModel:
-            currTurbineCodes = group[Field_CodeOfTurbine].unique().tolist()
-            currTurbineModeInfo = self.common.getTurbineModelByCode(
-                turbineModelCode, self.turbineModelInfo)
-
-            currDataFrameOfTurbines = dataFrameOfTurbines[dataFrameOfTurbines[Field_CodeOfTurbine].isin(
-                currTurbineCodes)]
-
-            # 将 currTurbineInfos 转换为字典
-            currTurbineInfos_dict = turbrineInfos.set_index(Field_CodeOfTurbine)[Field_NameOfTurbine].to_dict()
-            # 使用 map 函数来填充 Field_NameOfTurbine 列
-            currDataFrameOfTurbines[Field_NameOfTurbine] = currDataFrameOfTurbines[Field_CodeOfTurbine].map(currTurbineInfos_dict).fillna("")
-
-            # 获取非全为空的列名
-            non_empty_cols = self.getUseColumns(currDataFrameOfTurbines, TemperatureColumns)
-            dataFrame = self.dataReprocess(currDataFrameOfTurbines,  non_empty_cols)
-            # non_empty_cols.remove(Field_Time)
-            non_empty_cols.remove(Field_ActiverPower)
-            non_empty_cols.remove(Field_EnvTemp)
-            non_empty_cols.remove(Field_NacTemp)
-
-            returnData= self.drawTemperatureGraph(dataFrame, outputAnalysisDir, conf, non_empty_cols,currTurbineModeInfo)
-
-            returnDatas.append(returnData)
-
-        returnResult = pd.concat(returnDatas, ignore_index=True)
-
-        return returnResult
-
-
-    def drawTemperatureGraph(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract, temperatureCols: list, turbineModelInfo: pd.Series):
-        """
-        大部件温度传感器分析
-        """
-        y_name = '温差(℃)'
-
-        outputDir = os.path.join(outputAnalysisDir, "GeneratorTemperature")
-        dir.create_directory(outputDir)
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_CodeOfTurbine)
-
-        result_rows = []
-        # Create output directories if they don't exist
-        for column in temperatureCols:
-            if not column in dataFrameMerge.columns:
-                continue
-            
-            # 判断是否是机舱温度本身
-            is_nac_temp = (column == Field_NacTemp)
-            
-            columnZH = TemperatureColumns.get(column)
-
-            if not is_nac_temp:
-                columnZH = f"{columnZH}温差"
-
-            outputPath = os.path.join(outputAnalysisDir, column)
-            dir.create_directory(outputPath)
-
-            fig = go.Figure()
-
-            # 获取 Plotly Express 中的颜色序列
-            colors = px.colors.sequential.Turbo
-
-            # 创建一个列表来存储各个风电机组的数据
-            turbine_data_list = []
-
-            # Add traces for each turbine
-            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_nac_temp and Field_NacTemp in group.columns:
-                    y_data = y_data - group[Field_NacTemp]
-                # ------------------
-                
-                fig.add_trace(go.Scatter(
-                    x=group[Field_PowerFloor],
-                    y=group[column],
-                    mode='lines',
-                    name=currTurbineInfo_group[Field_NameOfTurbine],
-                    # 从 'Rainbow' 色组中循环选择颜色
-                    line=dict(color=colors[idx % len(colors)])
-                ))
-                # 提取数据
-                turbine_data_total = {
-                    "engineName": currTurbineInfo_group[Field_NameOfTurbine],
-                    "engineCode": name,
-                    "xData": group[Field_PowerFloor].tolist(),
-                    "yData": group[column].tolist(),
-                    "color": colors[idx % len(colors)]
-                }
-                turbine_data_list.append(turbine_data_total)
-
-            # Update layout and axes
-            fig.update_layout(
-                title={'text': f'{columnZH}分布-{turbineModelInfo[Field_MachineTypeCode]}', 'x': 0.5},
-                xaxis_title='功率',
-                yaxis_title=y_name,
-                xaxis=dict(
-                    range=[
-                        self.axisLowerLimitActivePower,
-                        self.axisUpperLimitActivePower
-                    ],
-                    dtick=self.axisStepActivePower
-                ),
-                yaxis=dict(
-                    range=[0, 100],
-                    dtick=20
-                ),
-                # legend_title_text='Turbine',
-                legend=dict(
-                    orientation="h",  # Horizontal orientation
-                    xanchor="center",  # Anchor the legend to the center
-                    x=0.5,  # Position legend at the center of the x-axis
-                    y=-0.2,  # Position legend below the x-axis
-                    # itemsizing='constant',  # Keep the size of the legend entries constant
-                    # # Set the width of the legend items (in pixels)
-                    # itemwidth=50
-                )
-            )
-
-            # Save the plot as a PNG/HTML file
-            # filePathOfImage = os.path.join(outputPath, f"{columnZH}-{turbineModelInfo[Field_MillTypeCode]}.png")
-            # fig.write_image(filePathOfImage, scale=3)
-            # filePathOfHtml = os.path.join(outputPath, f"{columnZH}-{turbineModelInfo[Field_MillTypeCode]}.html")
-            #fig.write_html(filePathOfHtml)
-
-            engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
-            if isinstance(engineTypeCode, pd.Series):
-                engineTypeCode = engineTypeCode.iloc[0]
-
-            engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
-            if isinstance(engineTypeName, pd.Series):
-                engineTypeName = engineTypeName.iloc[0]
-            # 构建最终的JSON对象
-            json_output = {
-                "analysisTypeCode": "大部件温度传感器分析",
-                "typecode": turbineModelInfo[Field_MillTypeCode],
-                "engineCode": engineTypeCode,
-                "engineTypeName": engineTypeName,
-                "title": f'{columnZH}分布-{turbineModelInfo[Field_MachineTypeCode]}',
-                "xaixs": "功率(kW)",
-                "yaixs": y_name,
-                "data": turbine_data_list
-            }
-
-            # 将JSON对象保存到文件
-            output_json_path = os.path.join(outputPath,f"{column}_{turbineModelInfo[Field_MillTypeCode]}.json")
-            with open(output_json_path, 'w', encoding='utf-8') as f:
-                import json
-                json.dump(json_output, f, ensure_ascii=False, indent=4)
-
-            # result_rows.append({
-            #     Field_Return_TypeAnalyst: self.typeAnalyst(),
-            #     Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
-            #     Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
-            #     Field_CodeOfTurbine: Const_Output_Total,
-            #     Field_Return_FilePath: filePathOfImage,
-            #     Field_Return_IsSaveDatabase: False
-            # })
-
-            # result_rows.append({
-            #    Field_Return_TypeAnalyst: self.typeAnalyst(),
-            #    Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
-            #    Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
-            #    Field_CodeOfTurbine: Const_Output_Total,
-            #    Field_Return_FilePath: filePathOfHtml,
-            #    Field_Return_IsSaveDatabase: True
-            # })
-
-            # 如果需要返回DataFrame,可以包含文件路径
-            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: f'{column}_{turbineModelInfo[Field_MillTypeCode]}',
-                Field_Return_FilePath: output_json_path,
-                Field_Return_IsSaveDatabase: True
-            })
-
-            # Create individual plots with specific turbine highlighted
-            # for name, group in grouped:
-            for idx, (name, group) in enumerate(grouped):
-                single_fig = go.Figure()
-                currTurbineInfo_each = self.common.getTurbineInfo(
-                    conf.dataContract.dataFilter.powerFarmID, name, self.turbineInfo)
-
-                # 创建一个列表来存储各个风电机组的数据
-                turbine_data_list_each = []
-
-                # Add all other turbines in grey first
-                # for other_name, other_group in grouped:
-                for idx, (other_name, other_group) in enumerate(grouped):
-                    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_nac_temp and Field_NacTemp in other_group.columns:
-                            oy_data = oy_data - other_group[Field_NacTemp]
-                        # ---------------
-                        
-                        single_fig.add_trace(go.Scatter(
-                            x=ox_data,
-                            y=oy_data,
-                            mode='lines',
-                            name=tempTurbineInfo[Field_NameOfTurbine],
-                            line=dict(color='lightgrey', width=1),
-                            showlegend=False
-                        ))
-                        # 提取数据
-                        turbine_data_other_each = {
-                            "engineName": tempTurbineInfo[Field_NameOfTurbine],
-                            "engineCode": other_name,
-                            "xData": other_group[Field_PowerFloor].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_nac_temp and Field_NacTemp in group.columns:
-                    gy_data = gy_data - group[Field_NacTemp]
-                # ---------------
-
-                single_fig.add_trace(go.Scatter(
-                    x=gx_data,
-                    y=gy_data,
-                    mode='lines',
-                    # Make it slightly thicker for visibility
-                    line=dict(color='darkblue', width=2),
-                    showlegend=False  # Disable legend for cleaner look
-                ))
-
-                turbine_data_curr = {
-                    "engineName": currTurbineInfo_each[Field_NameOfTurbine],
-                    "engineCode": currTurbineInfo_each[Field_CodeOfTurbine],
-                    "xData": group[Field_PowerFloor].tolist(),
-                    "yData": group[column].tolist(),
-                }
-                turbine_data_list_each.append(turbine_data_curr)
-
-                # Update layout and axes for the individual plot
-                single_fig.update_layout(
-                    # title={'text': f'Turbine: {name}', 'x': 0.5},
-                    title={'text': f'{columnZH}分布: {currTurbineInfo_each[Field_NameOfTurbine]}', 'x': 0.5},
-                    xaxis_title='功率',
-                    yaxis_title=y_name,
-                    xaxis=dict(
-                        range=[0, Field_RatedPower],
-                        dtick=200
-                    ),
-                    yaxis=dict(
-                        range=[0, 100],
-                        dtick=20
-                    )
-                )
-
-                engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
-                if isinstance(engineTypeCode, pd.Series):
-                    engineTypeCode = engineTypeCode.iloc[0]
-
-                engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
-                if isinstance(engineTypeName, pd.Series):
-                    engineTypeName = engineTypeName.iloc[0]
-                # 构建最终的JSON对象
-                json_output = {
-                    "analysisTypeCode": "大部件温度传感器分析",
-                    "typecode": turbineModelInfo[Field_MillTypeCode],
-                    "engineCode": engineTypeCode,
-                    "engineTypeName": engineTypeName,
-                    "title": f'{columnZH}分布: {currTurbineInfo_each[Field_NameOfTurbine]}',
-                    "xaixs": "功率(kW)",
-                    "yaixs": y_name,
-                    "data": turbine_data_list_each
-                }
-
-                # 将JSON对象保存到文件
-                output_json_path_each = os.path.join(outputPath, f"{currTurbineInfo_each[Field_NameOfTurbine]}.json")
-                with open(output_json_path_each, 'w', encoding='utf-8') as f:
-                    import json
-                    json.dump(json_output, f, ensure_ascii=False, indent=4)
-
-                # filePathOfImage = os.path.join(
-                #     outputPath, f"{currTurbineInfo_each[Field_NameOfTurbine]}.png")
-                # single_fig.write_image(filePathOfImage, scale=3)
-                # filePathOfHtml = os.path.join(
-                #     outputPath, f"{name}.html")
-                # single_fig.write_html(filePathOfHtml)
-
-                # result_rows.append({
-                #     Field_Return_TypeAnalyst: self.typeAnalyst(),
-                #     Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
-                #     Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
-                #     Field_CodeOfTurbine: group[Field_CodeOfTurbine].iloc[0],
-                #     Field_Return_FilePath: filePathOfImage,
-                #     Field_Return_IsSaveDatabase: False
-                # })
-
-                # 如果需要返回DataFrame,可以包含文件路径
-                result_rows.append({
-                    Field_Return_TypeAnalyst: self.typeAnalyst(),
-                    Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
-                    Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
-                    Field_CodeOfTurbine: group[Field_CodeOfTurbine].iloc[0],
-                    Field_Return_FilePath: output_json_path_each,
-                    Field_Return_IsSaveDatabase: True
-                })
-
-                # result_rows.append({
-                #    Field_Return_TypeAnalyst: self.typeAnalyst(),
-                #    Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
-                #    Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
-                #    Field_CodeOfTurbine: group[Field_CodeOfTurbine].iloc[0],
-                #    Field_Return_FilePath: filePathOfHtml,
-                #    Field_Return_IsSaveDatabase: True
-                # })
-
-        for idx, (name, group) in enumerate(grouped):
-            # 绘制每台机组发电机的,驱动轴承温度、非驱动轴承温度、发电机轴承温度BIAS、发电机轴承温度和机舱温度BIAS 均与有功功率的折线图
-            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_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)
-
-        result_df = pd.DataFrame(result_rows)
-
-        return result_df
-
-    def drawGeneratorTemperature(self, dataFrame: pd.DataFrame, conf: Contract, yAxisDE, yAxisNDE, diffTemperature, xAxis, turbineCode, outputDir):
-        tempTurbineInfo1 = self.common.getTurbineInfo(
-            conf.dataContract.dataFilter.powerFarmID, turbineCode, self.turbineInfo)
-        turbineName = tempTurbineInfo1[Field_NameOfTurbine]
-        # 发电机驱动轴承温度 和 发电机非驱动轴承 温差
-        fieldBIAS_DE_NDE = 'BIAS_DE-NDE'
-        fieldBIAS_DE = 'BIAS_DE'
-        fieldBIAS_NDE = 'BIAS_NDE'
-
-        # Prepare Data
-        dataFrame[fieldBIAS_DE] = dataFrame[yAxisDE] - \
-            dataFrame[diffTemperature]
-        dataFrame[fieldBIAS_NDE] = dataFrame[yAxisNDE] - \
-            dataFrame[diffTemperature]
-        dataFrame[fieldBIAS_DE_NDE] = dataFrame[yAxisDE] - dataFrame[yAxisNDE]
-
-        # Create a plot with dual y-axes
-        fig = make_subplots(specs=[[{"secondary_y": True}]])
-
-        plot_data_list_each = []
-
-        # Plot DE Bearing Temperature
-        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[yAxisDE], name='驱动端轴承温度', line=dict(
-            color='blue')), secondary_y=False)
-
-        plot_data_curr = {
-            "Name": '驱动端轴承温度',
-            "xData": dataFrame[xAxis].tolist(),
-            "yData": dataFrame[yAxisDE].tolist(),
-            "color": 'blue',
-        }
-        plot_data_list_each.append(plot_data_curr)
-
-        # Plot NDE Bearing Temperature
-        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[yAxisNDE], name='非驱动端轴承温度', line=dict(
-            color='green')), secondary_y=False)
-
-        plot_data_curr = {
-            "Name": '非驱动端轴承温度',
-            "xData": dataFrame[xAxis].tolist(),
-            "yData": dataFrame[yAxisNDE].tolist(),
-            "color": 'green',
-        }
-        plot_data_list_each.append(plot_data_curr)
-
-        # Plot Temperature Differences
-        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[fieldBIAS_DE], name='驱动端轴承温度与机舱温度偏差', line=dict(
-            color='blue', dash='dot')), secondary_y=False)
-        plot_data_curr = {
-            "Name": '驱动端轴承温度与机舱温度偏差',
-            "xData": dataFrame[xAxis].tolist(),
-            "yData": dataFrame[fieldBIAS_DE].tolist(),
-            "color": 'blue',
-        }
-        plot_data_list_each.append(plot_data_curr)
-
-        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[fieldBIAS_NDE], name='非驱动端轴承温度与机舱温度偏差', line=dict(
-            color='green', dash='dot')), secondary_y=False)
-        plot_data_curr = {
-            "Name": '非驱动端轴承温度与机舱温度偏差',
-            "xData": dataFrame[xAxis].tolist(),
-            "yData": dataFrame[fieldBIAS_NDE].tolist(),
-            "color": 'green',
-        }
-        plot_data_list_each.append(plot_data_curr)
-
-        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[fieldBIAS_DE_NDE],
-                      name='驱动端轴承与非驱动端轴承温度偏差', line=dict(color='black', dash='dash')), secondary_y=False)
-        plot_data_curr = {
-            "Name": '驱动端轴承与非驱动端轴承温度偏差',
-            "xData": dataFrame[xAxis].tolist(),
-            "yData": dataFrame[fieldBIAS_DE_NDE].tolist(),
-            "color": 'black',
-        }
-        plot_data_list_each.append(plot_data_curr)
-
-        # Plot Nacelle Temperature
-        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[diffTemperature],
-                      name='机舱温度', line=dict(color='orange')), secondary_y=False)
-        plot_data_curr = {
-            "Name": '机舱温度',
-            "xData": dataFrame[xAxis].tolist(),
-            "yData": dataFrame[diffTemperature].tolist(),
-            "color": 'orange',
-        }
-        plot_data_list_each.append(plot_data_curr)
-
-        # Add horizontal reference lines
-        fig.add_hline(y=5, line_dash="dot", line_color="#FFDB58")
-        fig.add_hline(y=-5, line_dash="dot", line_color="#FFDB58")
-
-        fig.add_hline(y=15, line_dash="dot", line_color="red")
-        fig.add_hline(y=-15, line_dash="dot", line_color="red")
-
-        # Update layout
-        fig.update_layout(
-            title={'text': f'发电机温度偏差: {turbineName}'},
-            xaxis_title="功率",
-            yaxis_title='轴承温度 & 偏差',
-            legend_title='温度 & 偏差',
-            legend=dict(x=1.1, y=0.5, bgcolor='rgba(255, 255, 255, 0.5)'),
-            margin=dict(r=200)  # Adjust margin to fit legend
-        )
-
-        fig.update_yaxes(range=[-20, 100], secondary_y=False)
-
-
-        # 构建最终的JSON对象
-        json_output = {
-            "analysisTypeCode": "发电机温度传感器分析",
-            "turbineName": tempTurbineInfo1[Field_NameOfTurbine],
-            "turbineCode": tempTurbineInfo1[Field_CodeOfTurbine],
-            "title": f'发电机温度偏差: {turbineName}',
-            "xaixs": "功率(kW)",
-            "yaixs": '轴承温度 & 偏差(℃)',
-            "data": plot_data_list_each
-        }
-
-        # 将JSON对象保存到文件
-        output_json_path_each = os.path.join(outputDir, f"{turbineName}.json")
-        with open(output_json_path_each, 'w', encoding='utf-8') as f:
-            import json
-            json.dump(json_output, f, ensure_ascii=False, indent=4)
-        # Save the plot as a PNG/HTML file
-        # filePathOfImage = os.path.join(outputDir, f"{turbineName}.png")
-        # fig.write_image(filePathOfImage,  width=800, height=600, scale=3)
-        # filePathOfHtml = os.path.join(outputDir, f"{turbineName}.html")
-        # fig.write_html(filePathOfHtml)
-
-        result_rows1 = []
-        # result_rows1.append({
-        #     Field_Return_TypeAnalyst: self.typeAnalyst(),
-        #     Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
-        #     Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
-        #     Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
-        #     Field_Return_FilePath: filePathOfImage,
-        #     Field_Return_IsSaveDatabase: False
-        # })
-
-        # 如果需要返回DataFrame,可以包含文件路径
-        result_rows1.append({
-            Field_Return_TypeAnalyst: self.typeAnalyst(),
-            Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
-            Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
-            Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
-            Field_Return_FilePath: output_json_path_each,
-            Field_Return_IsSaveDatabase: True
-        })
-
-        # result_rows1.append({
-        #    Field_Return_TypeAnalyst: self.typeAnalyst(),
-        #    Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
-        #    Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
-        #    Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
-        #    Field_Return_FilePath: filePathOfHtml,
-        #    Field_Return_IsSaveDatabase: True
-        # })
-
-        return result_rows1
+import os
+
+import pandas as pd
+import plotly.express as px
+import plotly.graph_objects as go
+from algorithmContract.confBusiness import *
+from algorithmContract.contract import Contract
+from behavior.analystWithGoodBadLimitPoint import AnalystWithGoodBadLimitPoint
+from plotly.subplots import make_subplots
+from utils.directoryUtil import DirectoryUtil as dir
+
+
+class Generator:
+    def __init__(self) -> None:
+        self.fieldTemperatorOfDEBearing = None
+        self.fieldTemperatorOfNDEBearing = None
+
+
+TemperatureColumns = {Field_MainBearTemp: "主轴承温度",
+                     Field_GbMsBearTemp: "齿轮箱中速轴温度",
+                      Field_GbLsBearTemp: "齿轮箱低速轴温度",
+                      Field_GbHsBearTemp: "齿轮箱高速轴温度",
+                      Field_GeneratorDE: "发电机驱动端轴承温度",
+                      Field_GeneratorNDE: "发电机非驱动端轴承温度",
+                      Field_GenWiTemp1: "发电机绕组温度"}
+
+GeneratorTemperatureAnslysisColumns = [
+    Field_GeneratorDE, Field_GeneratorNDE, Field_NacTemp]
+
+
+class TemperatureLargeComponentsAnalyst(AnalystWithGoodBadLimitPoint):
+    """
+    风电机组大部件温升分析
+    """
+
+    def typeAnalyst(self):
+        return "temperature_large_components"
+
+    def getUseColumns(self, dataFrame: pd.DataFrame, temperatureColumns: list[dict]):
+        # 获取非全为空的列名
+        non_empty_cols = self.getNoneEmptyFields(dataFrame, temperatureColumns)
+
+        useCols = []
+        # useCols.append(Field_Time)
+        useCols.append(Field_ActiverPower)
+
+        if not self.common.isNone(Field_EnvTemp) and Field_EnvTemp in dataFrame.columns:
+            useCols.append(Field_EnvTemp)
+
+        if not self.common.isNone(Field_NacTemp) and Field_NacTemp in dataFrame.columns:
+            useCols.append(Field_NacTemp)
+
+        useCols.extend(non_empty_cols)
+
+        return useCols
+
+    def getNoneEmptyFields(self, dataFrame: pd.DataFrame, temperatureColumns: dict) -> list:
+        # 使用set和列表推导式来获取在DataFrame中存在的字段
+        existing_fields = [
+            key for key in TemperatureColumns.keys() if key in dataFrame.columns
+        ]
+        # 检查指定列中非全为空的列
+        non_empty_columns = dataFrame[existing_fields].apply(
+            lambda x: x.notnull().any(), axis=0)
+        # 获取非全为空的列名
+        noneEmptyFields = non_empty_columns[non_empty_columns].index.tolist()
+        return noneEmptyFields
+
+    def dataReprocess(self, dataFrame: pd.DataFrame,  non_empty_cols: list):
+        # Initialize an empty df for aggregation
+        agg_dict = {col: 'median' for col in non_empty_cols}
+
+        # Group by 'power_floor' and aggregate
+        grouped = dataFrame.groupby([Field_PowerFloor, Field_CodeOfTurbine, Field_NameOfTurbine]).agg(agg_dict).reset_index()
+
+        # Sort by 'power_floor'
+        grouped.sort_values(
+            [Field_PowerFloor, Field_CodeOfTurbine, Field_NameOfTurbine], inplace=True)
+
+        return grouped
+
+    def turbinesAnalysis(self,  outputAnalysisDir, conf: Contract, turbineCodes):
+        select=[Field_DeviceCode, Field_Time, Field_WindSpeed, Field_ActiverPower]
+        select=select+[Field_EnvTemp,Field_NacTemp]+list(TemperatureColumns.keys())
+
+        dictionary = self.processTurbineData(turbineCodes, conf,select )
+        dataFrameOfTurbines = self.userDataFrame(
+            dictionary, conf.dataContract.configAnalysis, self)
+
+        turbrineInfos = self.common.getTurbineInfos(
+            conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
+
+        groupedOfTurbineModel = turbrineInfos.groupby(Field_MillTypeCode)
+
+        returnDatas = []
+        for turbineModelCode, group in groupedOfTurbineModel:
+            currTurbineCodes = group[Field_CodeOfTurbine].unique().tolist()
+            currTurbineModeInfo = self.common.getTurbineModelByCode(
+                turbineModelCode, self.turbineModelInfo)
+
+            currDataFrameOfTurbines = dataFrameOfTurbines[dataFrameOfTurbines[Field_CodeOfTurbine].isin(
+                currTurbineCodes)]
+
+            # 将 currTurbineInfos 转换为字典
+            currTurbineInfos_dict = turbrineInfos.set_index(Field_CodeOfTurbine)[Field_NameOfTurbine].to_dict()
+            # 使用 map 函数来填充 Field_NameOfTurbine 列
+            currDataFrameOfTurbines[Field_NameOfTurbine] = currDataFrameOfTurbines[Field_CodeOfTurbine].map(currTurbineInfos_dict).fillna("")
+
+            # 获取非全为空的列名
+            non_empty_cols = self.getUseColumns(currDataFrameOfTurbines, TemperatureColumns)
+            dataFrame = self.dataReprocess(currDataFrameOfTurbines,  non_empty_cols)
+            # non_empty_cols.remove(Field_Time)
+            non_empty_cols.remove(Field_ActiverPower)
+            non_empty_cols.remove(Field_EnvTemp)
+            non_empty_cols.remove(Field_NacTemp)
+
+            returnData= self.drawTemperatureGraph(dataFrame, outputAnalysisDir, conf, non_empty_cols,currTurbineModeInfo)
+
+            returnDatas.append(returnData)
+
+        returnResult = pd.concat(returnDatas, ignore_index=True)
+
+        return returnResult
+
+
+    def drawTemperatureGraph(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract, temperatureCols: list, turbineModelInfo: pd.Series):
+        """
+        大部件温度传感器分析
+        """
+        y_name = '温差(℃)'
+
+        outputDir = os.path.join(outputAnalysisDir, "GeneratorTemperature")
+        dir.create_directory(outputDir)
+        # 按设备名分组数据
+        grouped = dataFrameMerge.groupby(Field_CodeOfTurbine)
+
+        result_rows = []
+        # Create output directories if they don't exist
+        for column in temperatureCols:
+            if not column in dataFrameMerge.columns:
+                continue
+            
+            # 判断是否是机舱温度本身
+            is_nac_temp = (column == Field_EnvTemp)
+            
+            columnZH = TemperatureColumns.get(column)
+
+            if not is_nac_temp:
+                columnZH = f"{columnZH}温差"
+
+            outputPath = os.path.join(outputAnalysisDir, column)
+            dir.create_directory(outputPath)
+
+            fig = go.Figure()
+
+            # 获取 Plotly Express 中的颜色序列
+            colors = px.colors.sequential.Turbo
+
+            # 创建一个列表来存储各个风电机组的数据
+            turbine_data_list = []
+
+            # Add traces for each turbine
+            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_nac_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],
+                    mode='lines',
+                    name=currTurbineInfo_group[Field_NameOfTurbine],
+                    # 从 'Rainbow' 色组中循环选择颜色
+                    line=dict(color=colors[idx % len(colors)])
+                ))
+                # 提取数据
+                turbine_data_total = {
+                    "engineName": currTurbineInfo_group[Field_NameOfTurbine],
+                    "engineCode": name,
+                    "xData": group[Field_PowerFloor].tolist(),
+                    "yData": y_data.tolist(),
+                    "color": colors[idx % len(colors)]
+                }
+                turbine_data_list.append(turbine_data_total)
+
+            # Update layout and axes
+            fig.update_layout(
+                title={'text': f'{columnZH}分布-{turbineModelInfo[Field_MachineTypeCode]}', 'x': 0.5},
+                xaxis_title='功率',
+                yaxis_title=y_name,
+                xaxis=dict(
+                    range=[
+                        self.axisLowerLimitActivePower,
+                        self.axisUpperLimitActivePower
+                    ],
+                    dtick=self.axisStepActivePower
+                ),
+                yaxis=dict(
+                    range=[0, 100],
+                    dtick=20
+                ),
+                # legend_title_text='Turbine',
+                legend=dict(
+                    orientation="h",  # Horizontal orientation
+                    xanchor="center",  # Anchor the legend to the center
+                    x=0.5,  # Position legend at the center of the x-axis
+                    y=-0.2,  # Position legend below the x-axis
+                    # itemsizing='constant',  # Keep the size of the legend entries constant
+                    # # Set the width of the legend items (in pixels)
+                    # itemwidth=50
+                )
+            )
+
+            # Save the plot as a PNG/HTML file
+            # filePathOfImage = os.path.join(outputPath, f"{columnZH}-{turbineModelInfo[Field_MillTypeCode]}.png")
+            # fig.write_image(filePathOfImage, scale=3)
+            # filePathOfHtml = os.path.join(outputPath, f"{columnZH}-{turbineModelInfo[Field_MillTypeCode]}.html")
+            #fig.write_html(filePathOfHtml)
+
+            engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
+            if isinstance(engineTypeCode, pd.Series):
+                engineTypeCode = engineTypeCode.iloc[0]
+
+            engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
+            if isinstance(engineTypeName, pd.Series):
+                engineTypeName = engineTypeName.iloc[0]
+            # 构建最终的JSON对象
+            json_output = {
+                "analysisTypeCode": "大部件温度传感器分析",
+                "typecode": turbineModelInfo[Field_MillTypeCode],
+                "engineCode": engineTypeCode,
+                "engineTypeName": engineTypeName,
+                "title": f'{columnZH}分布-{turbineModelInfo[Field_MachineTypeCode]}',
+                "xaixs": "功率(kW)",
+                "yaixs": y_name,
+                "data": turbine_data_list
+            }
+
+            # 将JSON对象保存到文件
+            output_json_path = os.path.join(outputPath,f"{column}_{turbineModelInfo[Field_MillTypeCode]}.json")
+            with open(output_json_path, 'w', encoding='utf-8') as f:
+                import json
+                json.dump(json_output, f, ensure_ascii=False, indent=4)
+
+            # result_rows.append({
+            #     Field_Return_TypeAnalyst: self.typeAnalyst(),
+            #     Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
+            #     Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
+            #     Field_CodeOfTurbine: Const_Output_Total,
+            #     Field_Return_FilePath: filePathOfImage,
+            #     Field_Return_IsSaveDatabase: False
+            # })
+
+            # result_rows.append({
+            #    Field_Return_TypeAnalyst: self.typeAnalyst(),
+            #    Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
+            #    Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
+            #    Field_CodeOfTurbine: Const_Output_Total,
+            #    Field_Return_FilePath: filePathOfHtml,
+            #    Field_Return_IsSaveDatabase: True
+            # })
+
+            # 如果需要返回DataFrame,可以包含文件路径
+            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: f'{column}_{turbineModelInfo[Field_MillTypeCode]}',
+                Field_Return_FilePath: output_json_path,
+                Field_Return_IsSaveDatabase: True
+            })
+
+            # Create individual plots with specific turbine highlighted
+            # for name, group in grouped:
+            for idx, (name, group) in enumerate(grouped):
+                single_fig = go.Figure()
+                currTurbineInfo_each = self.common.getTurbineInfo(
+                    conf.dataContract.dataFilter.powerFarmID, name, self.turbineInfo)
+
+                # 创建一个列表来存储各个风电机组的数据
+                turbine_data_list_each = []
+
+                # Add all other turbines in grey first
+                # for other_name, other_group in grouped:
+                for idx, (other_name, other_group) in enumerate(grouped):
+                    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_nac_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,
+                            mode='lines',
+                            name=tempTurbineInfo[Field_NameOfTurbine],
+                            line=dict(color='lightgrey', width=1),
+                            showlegend=False
+                        ))
+                        # 提取数据
+                        turbine_data_other_each = {
+                            "engineName": tempTurbineInfo[Field_NameOfTurbine],
+                            "engineCode": other_name,
+                            "xData": other_group[Field_PowerFloor].tolist(),
+                            "yData": oy_data.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_nac_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,
+                    mode='lines',
+                    # Make it slightly thicker for visibility
+                    line=dict(color='darkblue', width=2),
+                    showlegend=False  # Disable legend for cleaner look
+                ))
+
+                turbine_data_curr = {
+                    "engineName": currTurbineInfo_each[Field_NameOfTurbine],
+                    "engineCode": currTurbineInfo_each[Field_CodeOfTurbine],
+                    "xData": group[Field_PowerFloor].tolist(),
+                    "yData": gy_data.tolist(),
+                }
+                turbine_data_list_each.append(turbine_data_curr)
+
+                # Update layout and axes for the individual plot
+                single_fig.update_layout(
+                    # title={'text': f'Turbine: {name}', 'x': 0.5},
+                    title={'text': f'{columnZH}分布: {currTurbineInfo_each[Field_NameOfTurbine]}', 'x': 0.5},
+                    xaxis_title='功率',
+                    yaxis_title=y_name,
+                    xaxis=dict(
+                        range=[0, Field_RatedPower],
+                        dtick=200
+                    ),
+                    yaxis=dict(
+                        range=[0, 100],
+                        dtick=20
+                    )
+                )
+
+                engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
+                if isinstance(engineTypeCode, pd.Series):
+                    engineTypeCode = engineTypeCode.iloc[0]
+
+                engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
+                if isinstance(engineTypeName, pd.Series):
+                    engineTypeName = engineTypeName.iloc[0]
+                # 构建最终的JSON对象
+                json_output = {
+                    "analysisTypeCode": "大部件温度传感器分析",
+                    "typecode": turbineModelInfo[Field_MillTypeCode],
+                    "engineCode": engineTypeCode,
+                    "engineTypeName": engineTypeName,
+                    "title": f'{columnZH}分布: {currTurbineInfo_each[Field_NameOfTurbine]}',
+                    "xaixs": "功率(kW)",
+                    "yaixs": y_name,
+                    "data": turbine_data_list_each
+                }
+
+                # 将JSON对象保存到文件
+                output_json_path_each = os.path.join(outputPath, f"{currTurbineInfo_each[Field_NameOfTurbine]}.json")
+                with open(output_json_path_each, 'w', encoding='utf-8') as f:
+                    import json
+                    json.dump(json_output, f, ensure_ascii=False, indent=4)
+
+                # filePathOfImage = os.path.join(
+                #     outputPath, f"{currTurbineInfo_each[Field_NameOfTurbine]}.png")
+                # single_fig.write_image(filePathOfImage, scale=3)
+                # filePathOfHtml = os.path.join(
+                #     outputPath, f"{name}.html")
+                # single_fig.write_html(filePathOfHtml)
+
+                # result_rows.append({
+                #     Field_Return_TypeAnalyst: self.typeAnalyst(),
+                #     Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
+                #     Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
+                #     Field_CodeOfTurbine: group[Field_CodeOfTurbine].iloc[0],
+                #     Field_Return_FilePath: filePathOfImage,
+                #     Field_Return_IsSaveDatabase: False
+                # })
+
+                # 如果需要返回DataFrame,可以包含文件路径
+                result_rows.append({
+                    Field_Return_TypeAnalyst: self.typeAnalyst(),
+                    Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
+                    Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
+                    Field_CodeOfTurbine: group[Field_CodeOfTurbine].iloc[0],
+                    Field_Return_FilePath: output_json_path_each,
+                    Field_Return_IsSaveDatabase: True
+                })
+
+                # result_rows.append({
+                #    Field_Return_TypeAnalyst: self.typeAnalyst(),
+                #    Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
+                #    Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
+                #    Field_CodeOfTurbine: group[Field_CodeOfTurbine].iloc[0],
+                #    Field_Return_FilePath: filePathOfHtml,
+                #    Field_Return_IsSaveDatabase: True
+                # })
+
+        for idx, (name, group) in enumerate(grouped):
+            # 绘制每台机组发电机的,驱动轴承温度、非驱动轴承温度、发电机轴承温度BIAS、发电机轴承温度和机舱温度BIAS 均与有功功率的折线图
+            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
+            result_rows1 = self.drawGeneratorTemperature(
+                group, conf, Field_GeneratorDE, Field_GeneratorNDE, refFieldTemperature, Field_PowerFloor, name, outputDir)
+            result_rows.extend(result_rows1)
+
+        result_df = pd.DataFrame(result_rows)
+
+        return result_df
+
+    def drawGeneratorTemperature(self, dataFrame: pd.DataFrame, conf: Contract, yAxisDE, yAxisNDE, diffTemperature, xAxis, turbineCode, outputDir):
+        tempTurbineInfo1 = self.common.getTurbineInfo(
+            conf.dataContract.dataFilter.powerFarmID, turbineCode, self.turbineInfo)
+        turbineName = tempTurbineInfo1[Field_NameOfTurbine]
+        # 发电机驱动轴承温度 和 发电机非驱动轴承 温差
+        fieldBIAS_DE_NDE = 'BIAS_DE-NDE'
+        fieldBIAS_DE = 'BIAS_DE'
+        fieldBIAS_NDE = 'BIAS_NDE'
+
+        # Prepare Data
+        dataFrame[fieldBIAS_DE] = dataFrame[yAxisDE] - \
+            dataFrame[diffTemperature]
+        dataFrame[fieldBIAS_NDE] = dataFrame[yAxisNDE] - \
+            dataFrame[diffTemperature]
+        dataFrame[fieldBIAS_DE_NDE] = dataFrame[yAxisDE] - dataFrame[yAxisNDE]
+
+        # Create a plot with dual y-axes
+        fig = make_subplots(specs=[[{"secondary_y": True}]])
+
+        plot_data_list_each = []
+
+        # Plot DE Bearing Temperature
+        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[yAxisDE], name='驱动端轴承温度', line=dict(
+            color='blue')), secondary_y=False)
+
+        plot_data_curr = {
+            "Name": '驱动端轴承温度',
+            "xData": dataFrame[xAxis].tolist(),
+            "yData": dataFrame[yAxisDE].tolist(),
+            "color": 'blue',
+        }
+        plot_data_list_each.append(plot_data_curr)
+
+        # Plot NDE Bearing Temperature
+        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[yAxisNDE], name='非驱动端轴承温度', line=dict(
+            color='green')), secondary_y=False)
+
+        plot_data_curr = {
+            "Name": '非驱动端轴承温度',
+            "xData": dataFrame[xAxis].tolist(),
+            "yData": dataFrame[yAxisNDE].tolist(),
+            "color": 'green',
+        }
+        plot_data_list_each.append(plot_data_curr)
+
+        # Plot Temperature Differences
+        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[fieldBIAS_DE], name='驱动端轴承温度与机舱温度偏差', line=dict(
+            color='blue', dash='dot')), secondary_y=False)
+        plot_data_curr = {
+            "Name": '驱动端轴承温度与机舱温度偏差',
+            "xData": dataFrame[xAxis].tolist(),
+            "yData": dataFrame[fieldBIAS_DE].tolist(),
+            "color": 'blue',
+        }
+        plot_data_list_each.append(plot_data_curr)
+
+        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[fieldBIAS_NDE], name='非驱动端轴承温度与机舱温度偏差', line=dict(
+            color='green', dash='dot')), secondary_y=False)
+        plot_data_curr = {
+            "Name": '非驱动端轴承温度与机舱温度偏差',
+            "xData": dataFrame[xAxis].tolist(),
+            "yData": dataFrame[fieldBIAS_NDE].tolist(),
+            "color": 'green',
+        }
+        plot_data_list_each.append(plot_data_curr)
+
+        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[fieldBIAS_DE_NDE],
+                      name='驱动端轴承与非驱动端轴承温度偏差', line=dict(color='black', dash='dash')), secondary_y=False)
+        plot_data_curr = {
+            "Name": '驱动端轴承与非驱动端轴承温度偏差',
+            "xData": dataFrame[xAxis].tolist(),
+            "yData": dataFrame[fieldBIAS_DE_NDE].tolist(),
+            "color": 'black',
+        }
+        plot_data_list_each.append(plot_data_curr)
+
+        # Plot Nacelle Temperature
+        fig.add_trace(go.Scatter(x=dataFrame[xAxis], y=dataFrame[diffTemperature],
+                      name='机舱温度', line=dict(color='orange')), secondary_y=False)
+        plot_data_curr = {
+            "Name": '机舱温度',
+            "xData": dataFrame[xAxis].tolist(),
+            "yData": dataFrame[diffTemperature].tolist(),
+            "color": 'orange',
+        }
+        plot_data_list_each.append(plot_data_curr)
+
+        # Add horizontal reference lines
+        fig.add_hline(y=5, line_dash="dot", line_color="#FFDB58")
+        fig.add_hline(y=-5, line_dash="dot", line_color="#FFDB58")
+
+        fig.add_hline(y=15, line_dash="dot", line_color="red")
+        fig.add_hline(y=-15, line_dash="dot", line_color="red")
+
+        # Update layout
+        fig.update_layout(
+            title={'text': f'发电机温度偏差: {turbineName}'},
+            xaxis_title="功率",
+            yaxis_title='轴承温度 & 偏差',
+            legend_title='温度 & 偏差',
+            legend=dict(x=1.1, y=0.5, bgcolor='rgba(255, 255, 255, 0.5)'),
+            margin=dict(r=200)  # Adjust margin to fit legend
+        )
+
+        fig.update_yaxes(range=[-20, 100], secondary_y=False)
+
+
+        # 构建最终的JSON对象
+        json_output = {
+            "analysisTypeCode": "发电机温度传感器分析",
+            "turbineName": tempTurbineInfo1[Field_NameOfTurbine],
+            "turbineCode": tempTurbineInfo1[Field_CodeOfTurbine],
+            "title": f'发电机温度偏差: {turbineName}',
+            "xaixs": "功率(kW)",
+            "yaixs": '轴承温度 & 偏差(℃)',
+            "data": plot_data_list_each
+        }
+
+        # 将JSON对象保存到文件
+        output_json_path_each = os.path.join(outputDir, f"{turbineName}.json")
+        with open(output_json_path_each, 'w', encoding='utf-8') as f:
+            import json
+            json.dump(json_output, f, ensure_ascii=False, indent=4)
+        # Save the plot as a PNG/HTML file
+        # filePathOfImage = os.path.join(outputDir, f"{turbineName}.png")
+        # fig.write_image(filePathOfImage,  width=800, height=600, scale=3)
+        # filePathOfHtml = os.path.join(outputDir, f"{turbineName}.html")
+        # fig.write_html(filePathOfHtml)
+
+        result_rows1 = []
+        # result_rows1.append({
+        #     Field_Return_TypeAnalyst: self.typeAnalyst(),
+        #     Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
+        #     Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
+        #     Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
+        #     Field_Return_FilePath: filePathOfImage,
+        #     Field_Return_IsSaveDatabase: False
+        # })
+
+        # 如果需要返回DataFrame,可以包含文件路径
+        result_rows1.append({
+            Field_Return_TypeAnalyst: self.typeAnalyst(),
+            Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
+            Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
+            Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
+            Field_Return_FilePath: output_json_path_each,
+            Field_Return_IsSaveDatabase: True
+        })
+
+        # result_rows1.append({
+        #    Field_Return_TypeAnalyst: self.typeAnalyst(),
+        #    Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
+        #    Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
+        #    Field_CodeOfTurbine: dataFrame[Field_CodeOfTurbine].iloc[0],
+        #    Field_Return_FilePath: filePathOfHtml,
+        #    Field_Return_IsSaveDatabase: True
+        # })
+
+        return result_rows1