浏览代码

新增修改转json算法

wenjia Li 4 月之前
父节点
当前提交
ab1adb6a7f

+ 55 - 8
dataAnalysisBusiness/algorithm/cpTrendAnalyst.py

@@ -109,30 +109,77 @@ class CpTrendAnalyst(AnalystWithGoodPoint):
                 margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
             )
 
+            # 确保从 Series 中提取的是具体的值
+            engineTypeCode = currTurbineInfo.get(Field_MillTypeCode, "")
+            if isinstance(engineTypeCode, pd.Series):
+                engineTypeCode = engineTypeCode.iloc[0]
+
+            engineTypeName = currTurbineInfo.get(Field_MachineTypeCode, "")
+            if isinstance(engineTypeName, pd.Series):
+                engineTypeName = engineTypeName.iloc[0]
+            # 构建最终的JSON对象
+            json_output = {
+                "analysisTypeCode": "风能利用系数时序分析",
+                "engineCode":  engineTypeCode,    
+                "engineTypeName": engineTypeName, 
+                "xaixs": "时间",
+                "yaixs": "风能利用系数",
+                "data": [{
+                        "engineName":currTurbineInfo[Field_NameOfTurbine],
+                        "engineCode":turbineCode,
+                        "title":f'机组-{currTurbineInfo[Field_NameOfTurbine]}',
+                        "xData": filtered_group[Field_YearMonthDay].tolist(),
+                        "yData": filtered_group[Field_Cp].tolist(),
+                        "color":'lightgray',
+                        "width":2,
+                        "type":"box_plot",
+                        "medians": {
+                                "x": medians.index.tolist(),  # 中位数的 x 轴数据
+                                "y": medians.values.tolist(),  # 中位数的 y 轴数据
+                                "mode":'markers',
+                                "color":'orange', 
+                                "size":3
+                                }
+                        }] 
+            }
             # 保存图像
             filePathOfImage = os.path.join(outputAnalysisDir, f"{currTurbineInfo[Field_NameOfTurbine]}.png")
             fig.write_image(filePathOfImage, scale=3)
-            filePathOfHtml = os.path.join(outputAnalysisDir, f"{currTurbineInfo[Field_NameOfTurbine]}.html")
-            fig.write_html(filePathOfHtml)
-
+            # filePathOfHtml = os.path.join(outputAnalysisDir, f"{currTurbineInfo[Field_NameOfTurbine]}.html")
+            # fig.write_html(filePathOfHtml)
+            # 将JSON对象保存到文件
+            output_json_path = os.path.join(outputAnalysisDir, f"{currTurbineInfo[Field_NameOfTurbine]}.json")
+            with open(output_json_path, 'w', encoding='utf-8') as f:
+                import json
+                json.dump(json_output, f, ensure_ascii=False, indent=4)
+
+            # 如果需要返回DataFrame,可以包含文件路径
             result_rows.append({
                 Field_Return_TypeAnalyst: self.typeAnalyst(),
                 Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
                 Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
                 Field_CodeOfTurbine: turbineCode,
-                Field_Return_FilePath: filePathOfImage,
-                Field_Return_IsSaveDatabase: False
+                Field_Return_FilePath: output_json_path,
+                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: turbineCode,
-                Field_Return_FilePath: filePathOfHtml,
-                Field_Return_IsSaveDatabase: True
+                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: turbineCode,
+            #     Field_Return_FilePath: filePathOfHtml,
+            #     Field_Return_IsSaveDatabase: True
+            # })
+
         result_df = pd.DataFrame(result_rows)
 
         return result_df

+ 18 - 14
dataAnalysisBusiness/algorithm/powerCurveAnalyst.py

@@ -77,12 +77,16 @@ class PowerCurveAnalyst(AnalystWithGoodPoint):
         })
 
         for turbineCode in turbineCodes:
+            data:pd.DataFrame=powerCurveDataOfTurbines[powerCurveDataOfTurbines[Field_CodeOfTurbine]==turbineCode]
+            jsonFileName2 = f"功率曲线数据-{data[Field_NameOfTurbine].iloc[0]}.json"
+            jsonFilePath2 = os.path.join(outputAnalysisDir, jsonFileName2)
+            JsonUtil.write_json(jsonDictionary, file_path=jsonFilePath2)
             result_rows.append({
                 Field_Return_TypeAnalyst: self.typeAnalyst(),
                 Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
                 Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
                 Field_CodeOfTurbine: turbineCode,
-                Field_Return_FilePath: jsonFilePath,
+                Field_Return_FilePath: jsonFilePath2,
                 Field_Return_IsSaveDatabase: True
             })
 
@@ -91,7 +95,7 @@ class PowerCurveAnalyst(AnalystWithGoodPoint):
 
     def convert2Json(self, turbineModelInfo: pd.Series,turbineCodes, dataFrameOfTurbines: pd.DataFrame, dataFrameOfContract: pd.DataFrame):
         result = {
-            "analysisTypeCode": self.typeAnalyst(),
+            "analysisTypeCode":"功率曲线分析",
             "engineTypeCode":  turbineModelInfo[Field_MillTypeCode] ,
             "engineTypeName": turbineModelInfo[Field_MachineTypeCode] ,
             "data": []
@@ -349,10 +353,10 @@ class PowerCurveAnalyst(AnalystWithGoodPoint):
         pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
         fig.write_image(pngFilePath, scale=3)
 
-        # 保存HTML
-        htmlFileName = f"{turbineName[0]}.html"
-        htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
-        fig.write_html(htmlFilePath)
+        # # 保存HTML
+        # htmlFileName = f"{turbineName[0]}.html"
+        # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
+        # fig.write_html(htmlFilePath)
 
         result_rows = []
         result_rows.append({
@@ -364,14 +368,14 @@ class PowerCurveAnalyst(AnalystWithGoodPoint):
             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: turbineName[1],
-            Field_Return_FilePath: htmlFilePath,
-            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: turbineName[1],
+        #     Field_Return_FilePath: htmlFilePath,
+        #     Field_Return_IsSaveDatabase: False
+        # })
 
         result_df = pd.DataFrame(result_rows)
         return result_df

+ 254 - 134
dataAnalysisBusiness/algorithm/yawErrorAnalyst.py

@@ -19,174 +19,266 @@ class YawErrorAnalyst(AnalystWithGoodPoint):
     fieldPowerMax = 'max_power'
     fieldPowerMin = 'min_power'
     fieldPowerMedian = 'median_power'
-    fieldPowerGT0p = 'power_gt_0'
-    fieldPowerGT80p = 'power_gt_80p'
-    fieldPowerRatio0p = 'ratio_0'
-    fieldPowerRatio80p = 'ratio_80p'
-    fieldSlop = 'slop'
+    fieldYawError = 'yaw_error'
+    fieldStep = 'wsstep'
+    fieldK = 'k' 
+    fieldWindSpeed = 'mean_WindSpeed'
+    fieldcount = 'point_num'
 
     def typeAnalyst(self):
         return "yaw_error"
 
     def selectColumns(self):
-        return [Field_DeviceCode,Field_Time,Field_WindSpeed,Field_ActiverPower,Field_AngleIncluded]
-
-    def filterCommon(self, dataFrame: pd.DataFrame, conf: Contract):
-        dataFrame = dataFrame[~((dataFrame[Field_AngleIncluded].abs() >= 15))]
+        return [Field_DeviceCode,Field_Time,Field_WindSpeed,Field_ActiverPower,Field_AngleIncluded,Field_PitchAngel1,Field_RotorSpeed, Field_GeneratorSpeed]
+
+    def filterCommon(self, dataFrame: pd.DataFrame,conf:Contract):
+        #-------------------1.物理筛选--------
+        # 使用 loc 方法获取 Field_RatedPower 列的值
+        fullpower  = self.turbineInfo[Field_RatedPower].iloc[0]
+        # rated_power  = self.turbineInfo.loc[self.turbineInfo[Field_CodeOfTurbine].isin(dataFrame[Field_CodeOfTurbine]), Field_RatedPower]
+        # fullpower=rated_power.iloc[0]
+        # rated_power=self.turbineInfo[Field_RatedPower]
+
+        # 删除小于0的有功功率
+        dataFrame = dataFrame[~(dataFrame[Field_ActiverPower] < 0)]
+        # 删除小于2.5的风速
+        dataFrame = dataFrame[~(dataFrame[Field_WindSpeed] < 2.5)]
+        # 删除有功小于额定功率-100,变桨角度大于0.2的数据
+        dataFrame = dataFrame[~((dataFrame[Field_ActiverPower] < fullpower - 100) & (dataFrame[Field_PitchAngel1] > 3))]
+        # 删除有功小于额定功率-40,变桨角度大于1的数据
+        dataFrame = dataFrame[~((dataFrame[Field_ActiverPower] < fullpower - 40) & (dataFrame[Field_PitchAngel1] > 5))]
+        # 删除对风角度小于-30,大于30的数据
+        dataFrame = dataFrame[~((dataFrame[Field_AngleIncluded].abs() > 30))]
+        #-------------------2.数学筛选--------
+        wsstep = 0.5
+        stdup = 6
+        stddown = 2.2
+        for wscol in np.arange(3, 15.25, wsstep/2):
+            dataFrame_lv = dataFrame[(dataFrame[Field_WindSpeed] >= (wscol-wsstep/4)) & (dataFrame[Field_WindSpeed] < (wscol+wsstep/4))]
+            meanpowr = np.mean(dataFrame_lv[Field_ActiverPower])
+            stddataws = np.std(dataFrame_lv[Field_ActiverPower])
+            index1 = dataFrame[(dataFrame[Field_WindSpeed] >= (wscol-wsstep/4)) & (dataFrame[Field_WindSpeed] < (wscol+wsstep/4))
+                             & ((dataFrame_lv[Field_ActiverPower] - meanpowr) > stdup * stddataws) | ((meanpowr - dataFrame_lv
+            [Field_ActiverPower]) > stddown * stddataws)].index
+            dataFrame.drop(index1, inplace=True)
 
         return dataFrame
 
-    def calculateYawError(self, dataFrame: pd.DataFrame, fieldAngleInclude, fieldActivePower):
+    def calculateYawError(self, dataFrame: pd.DataFrame, fieldAngleInclude,fieldActivePower, fieldWindSpeed):
+        
         dataFrame = dataFrame.dropna(
-            subset=[Field_NameOfTurbine, fieldAngleInclude, fieldActivePower])
-        # Calculate floor values and other transformations
-        dataFrame[self.fieldWindDirFloor] = np.floor(
-            dataFrame[fieldAngleInclude]).astype(int)
-        dataFrame[self.fieldPower] = dataFrame[fieldActivePower].astype(float)
-
-        # Calculate aggregated metrics for power
-        grouped = dataFrame.groupby(self.fieldWindDirFloor).agg({
-            Field_NameOfTurbine: ['min'],
-            self.fieldPower: ['mean', 'max', 'min', 'median', lambda x: (
-                x > 0).sum(), lambda x: (x > x.median()).sum()]
-        }).reset_index()
-
-        # Rename columns for clarity
-        grouped.columns = [self.fieldWindDirFloor, Field_NameOfTurbine, self.fieldPowerMean, self.fieldPowerMax,
-                           self.fieldPowerMin, self.fieldPowerMedian, self.fieldPowerGT0p, self.fieldPowerGT80p]
-
-        # Calculate total sums for conditions
-        power_gt_0_sum = grouped[self.fieldPowerGT0p].sum()
-        power_gt_80p_sum = grouped[self.fieldPowerGT80p].sum()
-
-        # Calculate ratios
-        grouped[self.fieldPowerRatio0p] = grouped[self.fieldPowerGT0p] / \
-            power_gt_0_sum
-        grouped[self.fieldPowerRatio80p] = grouped[self.fieldPowerGT80p] / \
-            power_gt_80p_sum
-
-        # Filter out zero ratios and calculate slope
-        grouped = grouped[grouped[self.fieldPowerRatio0p] > 0]
-        grouped[self.fieldSlop] = grouped[self.fieldPowerRatio80p] / \
-            grouped[self.fieldPowerRatio0p]
-
-        # Sort by wind direction floor
-        grouped.sort_values(self.fieldWindDirFloor, inplace=True)
-
-        # # Write to CSV
-        # grouped.to_csv(output_path, index=False)
-
-        return grouped
+            subset=[Field_NameOfTurbine, fieldAngleInclude, fieldActivePower,fieldWindSpeed])
+        df_math_yaw = dataFrame.copy()
+        # 偏航误差角度计算
+        degree_length = 20  # 对风角度最大值
+        yaw_data_out = np.empty((0, 4), float)
+        wsstep = 0.5
+        # 初始化一个字典来存储每 k 次循环的平均功率结果
+        power_dict = {}
+        for k in np.arange(3, 15.5, wsstep):
+            yaw_data_value_co = np.empty((0, 4), float)
+            yaw_data = df_math_yaw[(df_math_yaw[fieldWindSpeed] >= (k - 0.25)) & (df_math_yaw[fieldWindSpeed] < (k + 0.25))]
+            # print(yaw_data)
+            for m in np.arange((0 - degree_length), degree_length + 1, 0.5):
+                wd = yaw_data[(yaw_data[fieldAngleInclude] >= (m - 0.5)) & (yaw_data[fieldAngleInclude] < (m + 0.5))]
+                wd_row = wd.shape[0]
+                if wd_row < 10:
+                    continue
+                if wd_row >= 10:
+                    max_value_row = wd[fieldActivePower].idxmax()
+                    min_value_row = wd[fieldActivePower].idxmin()
+                    wd = wd.drop(max_value_row)
+                    wd = wd.drop(min_value_row)
+                wd_row = wd.shape[0]
+                mean_wd = wd[fieldAngleInclude].mean()
+                mean_power = wd[fieldActivePower].mean()
+                mean_ws = wd[fieldWindSpeed].mean()
+                yaw_data_value = [mean_power, mean_wd, mean_ws, wd_row]
+                yaw_data_value_co = np.vstack((yaw_data_value_co, yaw_data_value))
+                # 存储当前 k 和 m 对应的 mean_power
+                if k not in power_dict:
+                    power_dict[k] = {}
+                power_dict[k][m] = mean_power
+            # 检查 yaw_data_value_co 是否为空
+            if yaw_data_value_co.size == 0:
+                print(f"yaw_data_value_co k={k}是空的,无法计算argmax")
+                continue
+            else:
+                max_row = np.argmax(yaw_data_value_co[:, 0])
+                yaw_data_out = np.vstack((yaw_data_out, yaw_data_value_co[max_row, :]))
+
+        # 将结果转换为 DataFrame
+        result_df = pd.DataFrame(yaw_data_out, columns=[self.fieldPowerMean, self.fieldYawError,self.fieldWindSpeed,self.fieldcount])
+        # result_df.to_csv('D:\\dashengkeji\\project\\WTOAAM\\debug_tset_20241008\\result_df_JHS_.csv',index=False)
+        # 构建最终画图的 DataFrame
+        final_df = pd.DataFrame(columns=[self.fieldWindDirFloor] + [f'{k}-{k+wsstep}' for k in np.arange(4, 9, wsstep)])
+        rows = []
+        for m in np.arange((0 - degree_length), degree_length + 1, 1):
+            row = {self.fieldWindDirFloor: m}
+            for k in np.arange(4, 9, wsstep):
+                if k in power_dict and power_dict[k]:  # 检查 power_dict[k] 是否存在且不为空
+                    row[f'{k}-{k+wsstep}'] = power_dict[k].get(m, np.nan)
+                else:
+                    row[f'{k}-{k+wsstep}'] = np.nan  # 如果 power_dict[k] 为空,则填充 np.nan
+            rows.append(row)
+        final_df = pd.concat([final_df, pd.DataFrame(rows)], ignore_index=True)
+        return result_df,final_df
 
-    def poly_func(self, x, a, b, c, d, e):
-        return a * x**4 + b * x ** 3 + c * x ** 2 + d * x + e
 
     def turbinesAnalysis(self,  outputAnalysisDir, conf: Contract, turbineCodes):
         dictionary = self.processTurbineData(turbineCodes,conf,self.selectColumns())
         dataFrameMerge = self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self)
-        return self.yawErrorAnalysis(dataFrameMerge, outputAnalysisDir, conf)
+        turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
+        return self.yawErrorAnalysis(dataFrameMerge,turbineInfos, outputAnalysisDir, conf)
 
-    def yawErrorAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract):
+    def yawErrorAnalysis(self, dataFrameMerge: pd.DataFrame,turbineModelInfo: pd.Series, outputAnalysisDir, conf: Contract):
         # 检查所需列是否存在
-        required_columns = {Field_ActiverPower, Field_YawError}
+        required_columns = {Field_ActiverPower, Field_YawError,Field_WindSpeed,Field_PitchAngel1,Field_Cp,Field_TSR}
         if not required_columns.issubset(dataFrameMerge.columns):
             raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-
+        
         results = []
         result_rows = []
         grouped = dataFrameMerge.groupby(
             [Field_NameOfTurbine, Field_CodeOfTurbine])
-
+ 
         for name, group in grouped:
-            df = self.calculateYawError(
-                group, Field_YawError, Field_ActiverPower)
-
+            group = self.filterCommon(group,conf)
+            df,final_df = self.calculateYawError(
+                group, Field_YawError, Field_ActiverPower,Field_WindSpeed)
+            
             df.dropna(inplace=True)
-            # drop extreme value, the 3 largest and 3 smallest
-
-            df_ = df[df[self.fieldPowerRatio80p] > 0.000]
-
-            xdata = df_[self.fieldWindDirFloor]
-            ydata = df_[self.fieldSlop]
-
-            # make ydata smooth
-            ydata = ydata.rolling(7).median()[6:]
-            xdata = xdata[3:-3]
-
-            if len(xdata) <= 0 and len(ydata) <= 0:
-                continue
-
-            # Curve fitting
-            popt, pcov = curve_fit(self.poly_func, xdata, ydata)
-            # popt contains the optimized parameters a, b, and c
-            # Generate fitted y-dataFrame using the optimized parameters
-            fitted_ydata = self.poly_func(xdata, *popt)
-
-            # get the max value of fitted_ydata and its index
-            max_pos = fitted_ydata.idxmax()
-
-            # Create a subplot with two rows
-            fig = make_subplots(rows=2, cols=1)
-
-            # First subplot
-            fig.add_trace(
-                go.Scatter(x=df[self.fieldWindDirFloor], y=df[self.fieldSlop],
-                           mode='markers', name="Energy Gain", marker=dict(size=5)),
-                row=1, col=1
-            )
-            fig.add_trace(
-                go.Scatter(x=xdata, y=fitted_ydata, mode='lines',
-                           name="Fit", line=dict(color='red')),
-                row=1, col=1
-            )
-            fig.add_trace(
-                go.Scatter(x=[df[self.fieldWindDirFloor][max_pos]], y=[fitted_ydata[max_pos]],
-                           mode='markers', name="Max Pos:"+str(df[self.fieldWindDirFloor][max_pos]),
-                           marker=dict(size=20)),
-                row=1, col=1
-            )
-
-            # Second subplot
-            fig.add_trace(
-                go.Scatter(x=df[self.fieldWindDirFloor], y=df[self.fieldPowerRatio0p],
-                           mode='markers', name="Base Energy", marker=dict(size=5)),
-                row=2, col=1
-            )
-            fig.add_trace(
-                go.Scatter(x=df[self.fieldWindDirFloor], y=df[self.fieldPowerRatio80p],
-                           mode='markers', name="Slope Energy", marker=dict(size=5)),
-                row=2, col=1
-            )
+            
+            # final_df.dropna(inplace=True)
+            """
+            自动化选择tsr平稳段的风速段作为风速最后的筛选区间
+           
+            # 滑动窗口size
+            window_size=5
+            # 差分阈值
+            threshold=2
+            diff = group[Field_TSR].diff().abs()
+            stable_mask = diff.rolling(window=window_size).mean() < threshold
+            # print("diff的索引:", diff.index)
+            # print("stable_mask的索引:", stable_mask.index)
+            # 找到最长的平稳段
+            longest_stable_segment = None
+            max_length = 0
+            current_segment = None
+
+            for i in range(len(stable_mask)):
+                if i in stable_mask.index:
+                    if stable_mask[i]:
+                        if current_segment is None:
+                            current_segment = [i]
+                        else:
+                            current_segment.append(i)
+                    else:
+                        if current_segment is not None:
+                            if len(current_segment) > max_length:
+                                longest_stable_segment = current_segment
+                                max_length = len(current_segment)
+                            current_segment = None
+
+            if current_segment is not None and len(current_segment) > max_length:
+                longest_stable_segment = current_segment
+
+            if longest_stable_segment is not None:
+                start_idx = longest_stable_segment[0]
+                end_idx = longest_stable_segment[-1]
+                # 获取tsr平稳区间对应的风俗区间
+                if start_idx is not None and end_idx is not None:
+                    # 提取对应的windspeed区间
+                    stable_windspeed_segment = group.loc[start_idx:end_idx, Field_WindSpeed]
+                    # print(f"平稳段的起始索引: {start_idx}, 结束索引: {end_idx}")
+                    # print("对应的windspeed区间:")
+                    # print(stable_windspeed_segment)
+                    # 计算风速平稳段的最大值和最小值
+                    max_windspeed = stable_windspeed_segment.max()
+                    min_windspeed = stable_windspeed_segment.min()
+                    # print(f"风速平稳段的最大值: {max_windspeed}")
+                    # print(f"风速平稳段的最小值: {min_windspeed}")
+                else:
+                    print("未找到平稳段")
+             """      
+            # 筛选出 self.fieldWindSpeed 列的数值处于 4.5 到 8 区间内的行
+            filtered_df = df[(df[self.fieldWindSpeed] >= 4.5) & (df[self.fieldWindSpeed] <= 8)]
+            # 查找 df 中  self.fieldYawError列的平均值,并向下取整作为偏航误差
+            max_yaw_error = filtered_df[self.fieldYawError].mean()
+            max_yaw_error_floor = np.floor(max_yaw_error)
+
+            # 存储结果
+            results.append({
+                Field_NameOfTurbine: name[0],
+                Field_YawError: max_yaw_error_floor
+            })
 
-            # Update layout
+            #对 final_df 中的数据进行可视化
+            fig = go.Figure()
+            for col in final_df.columns[1:]:  # 跳过第一列 self.fieldWindDirFloor
+                fig.add_trace(go.Scatter(
+                    x=final_df[self.fieldWindDirFloor],
+                    y=final_df[col],
+                    mode='lines+markers',
+                    name=col
+                ))
+            
             fig.update_layout(
-                title={
-                    "text": f"Yaw Error Analysis: {name[0]}",
-                    "x": 0.5
-                },
-                showlegend=True,
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
+                title=f'偏航误差分析 {name[0]} ',
+                xaxis_title='偏航角度',
+                yaxis_title='平均功率',
+                legend_title='风速区间',
+                showlegend=True
             )
-
+            # 确保从 Series 中提取的是具体的值
+            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": "偏航误差分析",
+                    "engineCode": engineTypeCode,    
+                    "engineTypeName": engineTypeName, 
+                    "xaixs": "偏航角度",
+                    "yaixs": "有功功率(kw)",
+                    "data": [
+                        {
+                            "engineName": name[0],
+                            "engineCode": name[1],
+                            "title": f'静态偏航误差分析 {name[0]}',
+                            "xData": final_df[self.fieldWindDirFloor].tolist(),
+                            "yData": final_df[col].tolist(),
+                            "legend": col,
+                            "type": "lines+markers",
+                            
+                        }
+                        for col in final_df.columns[1:]  # 跳过第一列 self.fieldWindDirFloor
+                    ]
+                    }
+            
             # Save to file
             filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
             fig.write_image(filePathOfImage, scale=3)
-            filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
-            fig.write_html(filePathOfHtml)
-
-            # calc squear error of fitted_ydata and ydata
-            print(name[0], "\t", df[self.fieldWindDirFloor][max_pos])
-
-            resultOfTurbine = [name[0], df[self.fieldWindDirFloor][max_pos]]
-            results.append(resultOfTurbine)
-
+            # filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
+            # fig.write_html(filePathOfHtml)
+            # 将JSON对象保存到文件
+            output_json_path = os.path.join(outputAnalysisDir, f"{name[0]}.json")
+            with open(output_json_path, 'w', encoding='utf-8') as f:
+                import json
+                json.dump(json_output, f, ensure_ascii=False, indent=4)
+
+            # 如果需要返回DataFrame,可以包含文件路径
             result_rows.append({
                 Field_Return_TypeAnalyst: self.typeAnalyst(),
                 Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
                 Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
                 Field_CodeOfTurbine: name[1],
-                Field_Return_FilePath: filePathOfImage,
-                Field_Return_IsSaveDatabase: False
+                Field_Return_FilePath: output_json_path,
+                Field_Return_IsSaveDatabase: True
             })
 
             result_rows.append({
@@ -194,10 +286,37 @@ class YawErrorAnalyst(AnalystWithGoodPoint):
                 Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
                 Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
                 Field_CodeOfTurbine: name[1],
-                Field_Return_FilePath: filePathOfHtml,
+                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: name[1],
+            #     Field_Return_FilePath: filePathOfHtml,
+            #     Field_Return_IsSaveDatabase: True
+            
+            
+            # })
+            # # 初始化一个空的DataFrame,指定列名
+            # columns = [Field_NameOfTurbine, Field_YawError]
+            # dataFrameResult = pd.DataFrame(results, columns=columns)
+
+            # filePathOfYawErrorResult = os.path.join(
+            #     outputAnalysisDir, f"{name[0]}yaw_error_result{CSVSuffix}")
+            # df.to_csv(filePathOfYawErrorResult, index=False)
+
+            # result_rows.append({
+            #     Field_Return_TypeAnalyst: self.typeAnalyst(),
+            #     Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
+            #     Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
+            #     Field_CodeOfTurbine: name[1],
+            #     Field_Return_FilePath: filePathOfYawErrorResult,
+            #             Field_Return_IsSaveDatabase: True
+            # })
+
         # 初始化一个空的DataFrame,指定列名
         columns = [Field_NameOfTurbine, Field_YawError]
         dataFrameResult = pd.DataFrame(results, columns=columns)
@@ -218,3 +337,4 @@ class YawErrorAnalyst(AnalystWithGoodPoint):
         result_df = pd.DataFrame(result_rows)
 
         return result_df
+