chenhongyan1989 5 месяцев назад
Родитель
Сommit
038f3682bd

+ 0 - 1
.gitignore

@@ -43,7 +43,6 @@ codegen/
 **/build/
 **/dist/
 **/log/
-./log/
 
 **/*.egg-info/
 **/__pycache__/

+ 0 - 40
appService/README.MD

@@ -1,40 +0,0 @@
-# 服务安装
-
-## Windows服务安装(Windows操作系统)
-    1. 创建应用程序,执行命令:  pyinstaller --onefile app.py
-    2. 安装服务,在PowerShell中,执行命令:New-Service -Name MyService -BinaryPathName "E:\WorkSpace\SourceCode\WTOAAM\appService\dist\app.exe"
-   
-## Deamon服务安装(Linux操作系统)
-    1. 创建应用程序,执行命令:  pyinstaller --onefile app.py
-    2. 编写 Systemd 服务单元文件:创建一个以 .service 为后缀的 Systemd 服务单元文件,该文件包含了关于你的服务的配置信息。通常这些文件存放在 /etc/systemd/system/ 目录下。例如,创建一个名为 mydaemon.service 的服务单元文件,内容类似于:
-    [Unit]
-    Description=My Daemon Service
-    After=network.target
-
-    [Service]
-    Type=simple
-    ExecStart=/path/to/your/daemon/executable
-    Restart=always
-
-    [Install]
-    WantedBy=multi-user.target
-
-    其中:
-    Description:描述服务的简短说明。
-    After:指定服务应该在哪些其他服务之后启动。
-    Type:指定服务的类型,可以是 simple、forking、oneshot、dbus 等。
-    ExecStart:指定服务启动时执行的命令或可执行文件的路径。
-    Restart:指定服务在失败或意外终止后是否应该自动重启。
-    WantedBy:指定服务应该在何时启动。常见的是 multi-user.target,表示在系统引导时启动。
-
-    3. 启用和启动服务:通过执行以下命令启用和启动服务:
-    sudo systemctl enable mydaemon.service
-    sudo systemctl start mydaemon.service
-
-    4. 停止和重启服务:你可以使用 systemctl 命令停止和重启服务:
-    sudo systemctl stop mydaemon.service   # 停止服务
-    sudo systemctl restart mydaemon.service   # 重启服务
-
-    5. 查看服务状态:你可以使用 systemctl status 命令来查看服务的状态和相关信息:
-    systemctl status mydaemon.service
-

+ 0 - 4
appService/__init__.py

@@ -1,4 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from . import service
-__all__=['service']

+ 0 - 60
appService/app.py

@@ -1,60 +0,0 @@
-import sys
-import time
-import threading
-import servicemanager
-from argparse import ArgumentParser
-from appService.logUtil import LogUtil
-
-logUtil=LogUtil()
-logger=logUtil.getLogger()
-
-def parse_args():
-    logger.debug("test2")
-    parser = ArgumentParser(description="Run as a service.")
-    parser.add_argument("action", choices=["start", "stop", "restart", "status"],default="start")
-    parser.add_argument("--type", choices=["daemon", "service"], default="service")
-    logger.debug("test3")
-    return parser.parse_args()
-
-def main():               
-    if sys.platform != "win32" :
-        from appService.service.daemonService import DaemonService
-        args = parse_args()
-
-        daemon_service = DaemonService()
-        if args.action == "start":
-            daemon_service.start()
-        elif args.action == "stop":
-            daemon_service.stop()
-        elif args.action == "status":
-            daemon_service.status()
-
-    if sys.platform == "win32":
-        from appService.service.winService import WinService,CommandLine
-
-        servicemanager.Initialize()
-        servicemanager.PrepareToHostSingle(WinService)
-        servicemanager.StartServiceCtrlDispatcher()
-
-    # if args.type == "service":
-    #     from appService.service.winService import WinService,CommandLine
-        
-    #     if len(sys.argv) == 1:
-    #         servicemanager.Initialize()
-    #         servicemanager.PrepareToHostSingle(WinService)
-    #         servicemanager.StartServiceCtrlDispatcher()
-    #     else:
-    #         CommandLine(WinService)
-    # elif args.type == "daemon":
-    #     from appService.service.daemonService import DaemonService
-
-    #     daemon_service = DaemonService()
-    #     if args.action == "start":
-    #         daemon_service.start()
-    #     elif args.action == "stop":
-    #         daemon_service.stop()
-    #     elif args.action == "status":
-    #         daemon_service.status()
-
-if __name__ == "__main__":
-    main()

+ 0 - 37
appService/app.spec

@@ -1,37 +0,0 @@
-# -*- mode: python ; coding: utf-8 -*-
-
-
-a = Analysis(
-    ['app.py'],
-    pathex=[],
-    binaries=[],
-    datas=[],
-    hiddenimports=[],
-    hookspath=[],
-    hooksconfig={},
-    runtime_hooks=[],
-    excludes=[],
-    noarchive=False,
-)
-pyz = PYZ(a.pure)
-
-exe = EXE(
-    pyz,
-    a.scripts,
-    a.binaries,
-    a.datas,
-    [],
-    name='app',
-    debug=False,
-    bootloader_ignore_signals=False,
-    strip=False,
-    upx=True,
-    upx_exclude=[],
-    runtime_tmpdir=None,
-    console=True,
-    disable_windowed_traceback=False,
-    argv_emulation=False,
-    target_arch=None,
-    codesign_identity=None,
-    entitlements_file=None,
-)

+ 0 - 0
appService/create


+ 0 - 20
appService/logUtil.py

@@ -1,20 +0,0 @@
-import logging
-from logging.handlers import RotatingFileHandler
-
-class LogUtil:
-    def __init__(self):
-        # 配置logging
-        logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-
-        # 创建logger对象
-        self.logger = logging.getLogger('example_logger')
-
-        # 创建一个FileHandler来输出所有级别的日志到指定文件
-        log_file = r'./example.log'
-        file_handler = logging.FileHandler(log_file)
-        file_handler.setLevel(logging.DEBUG)  # 设置FileHandler的日志级别为DEBUG,以确保记录所有级别的日志
-        file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
-        self.logger.addHandler(file_handler)
-
-    def getLogger(self):
-        return self.logger

+ 0 - 0
appService/service/__init__.py


+ 0 - 28
appService/service/daemonService.py

@@ -1,28 +0,0 @@
-import sys
-import time
-import threading
-import pwd
-import signal
-
-class DaemonService:
-    def __init__(self):
-        self.stop_event = threading.Event()
-
-    def run(self):
-        while not self.stop_event.is_set():
-            print("Daemon is running...")
-            time.sleep(5)
-
-    def start(self):
-        self.stop_event.clear()
-        thread = threading.Thread(target=self.run)
-        thread.start()
-
-    def stop(self):
-        self.stop_event.set()
-
-    def status(self):
-        if self.stop_event.is_set():
-            print("Daemon is not running.")
-        else:
-            print("Daemon is running.")

+ 0 - 58
appService/service/winService.py

@@ -1,58 +0,0 @@
-import sys
-import time
-import threading
-import win32event
-import win32service
-import win32serviceutil
-from appService.logUtil import LogUtil
-
-logUtil=LogUtil()
-logger=logUtil.getLogger()
-
-def CommandLine(classService):
-    win32serviceutil.HandleCommandLine(classService)
-
-class WinService(win32serviceutil.ServiceFramework):
-    _svc_name_ = "MyService"
-    _svc_display_name_ = "My Service"
-    _svc_description_ = "This is a sample service."
-
-    def __init__(self, args):
-        logger.info("execute init")
-        try:
-            win32serviceutil.ServiceFramework.__init__(self, args)
-            self.stop_event = win32event.CreateEvent(None, 0, 0, None)
-        except Exception as ex:
-            logger.error(ex)
-
-    def SvcStop(self):
-        self.stop()
-
-    def SvcDoRun(self):
-        self.start()
-
-    def main(self):
-        while self.is_running:
-            logger.info("Service is running...")
-            time.sleep(10)
-
-    def start(self):
-        logger.info("execute start")
-        try:
-            self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
-            self.is_running = True
-            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
-            self.main()
-        except Exception as ex:
-            logger.error(ex)
-
-    def stop(self):
-        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
-        self.is_running = False
-        win32event.SetEvent(self.stop_event)
-
-    def status(self):
-        if self.is_running:
-            print("Service is running.")
-        else:
-            print("Service is not running.")

+ 0 - 100
conf_template/conf_powerfarm_template.json

@@ -1,100 +0,0 @@
-{
-    "name_PowerFarm": "长清风电场",
-    "rated_Power_Turbine_Unit_kW": 2000,
-    "rated_WindSpeed": 11,
-    "rotor_diameter": 105.0,
-    "rated_cut_in_windspeed":3, 
-    "rated_cut_out_windspeed":25,
-    "rotational_Speed_Ratio": 117.91,
-    "name_Output": "second level",
-    "time_Period_Unit_Second": 600,
-    "turbineInfoFilePathCSV": "F:/大生科技/数据/高本山10分钟数据/机组信息_风电场_gbs.csv",
-    "turbineGuaranteedPowerCurveFilePathCSV": "F:/大生科技/数据/高本山10分钟数据/curv_power_hetong_gbs.csv",
-    "inputFileDirectoryByCSV": "F:/大生科技/数据/高本山10分钟数据/gaobenshan-10min/",
-    "csvFileNameSplitStringForTurbine": ".csv",
-    "index_turbine": 0,
-    "filter": {
-        "filter_value_state_turbine": null,
-        "speed_wind_cut_in": 3,
-        "speed_wind_cut_out": 25,
-        "angle_pitch_min": 2,
-        "angle_pitch_max": null,
-        "active_power_min": 39,
-        "active_power_max": 2000,
-        "speed_generator_min": null,
-        "speed_generator_max": null,
-        "activePowerAvailable": [
-            1
-        ]
-    },
-    "graphSets": {
-        "generatorSpeed": {
-            "step": 200,
-            "min": 1000,
-            "max": 2000
-        },
-        "generatorTorque": {
-            "step": 2000,
-            "min": 0,
-            "max": 12000
-        },
-        "cp": {
-            "step": 0.5,
-            "min": 0,
-            "max": 2
-        },
-        "tsr": {
-            "step": 5,
-            "min": 0,
-            "max": 30
-        },
-        "pitchAngle": {
-            "step": 2,
-            "min": -1,
-            "max": 30
-        },
-        "activePower": {
-            "step": 250,
-            "min": 0,
-            "max": 2000
-        },
-        "generatorTemperature": {
-            "step": 10,
-            "min": -40,
-            "max": 100
-        }
-    },
-    "outputFileDirectory": "output",
-    "skip_row_number": 0,
-    "density_air": 1.222,
-    "name_Type_For_Analysis": "数据完整度",
-    "date_Begin": "2023-02-01 00:00:00",
-    "date_End": "2023-12-31 23:59:59",
-    "excludingMonths": null,
-    "turbine_Time": "时间",
-    "turbine_Name": null,
-    "speed_Wind": "30秒平均风速",
-    "power_Active": "有功功率",
-    "pitch_Angle1": "桨距角1",
-    "pitch_Angle2": "桨距角2",
-    "pitch_Angle3": "桨距角3",
-    "state_Turbine": null,
-    "speed_Generator": "发电机转速",
-    "speed_Rotor": "风轮转速",
-    "torque": null,
-    "direction_Wind": "60秒平均风向角",
-    "angle_included": "风向角",
-    "nacelle_Pos": "机舱位置",
-    "temperature_Env": "舱外温度",
-    "temperature_Nacelle": "舱内温度",
-    "Cabin_Vibrate_X": "机舱横向(左右)振动值(RMS)",
-    "Cabin_Vibrate_Y": "机舱前后(俯仰)振动值(RMS)",
-    "activePowerSet": "有功功率设定值",
-    "activePowerAvailable": "功率曲线可用",
-    "temperature_large_components": "低速轴承温度,高速轴承温度,齿轮箱入口油温,驱动前轴承温度,自由后轴承温度,发电机定子U温度",
-    "temperature_Generator": {
-        "yAxisDE": "低速轴承温度",
-        "yAxisNDE": "高速轴承温度",
-        "diffTemperature": "舱内温度"
-    }
-}

+ 0 - 152
conf_template/conf_template.json

@@ -1,152 +0,0 @@
-[
-    {
-        "configFilePath": "conf/conf_powerfarm_template.json",
-        "configAnalysis": [
-            {
-                "package": "algorithm.cabinVibrateAnalyst",
-                "className": "CabinVibrateAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.windSpeedFrequencyAnalyst",
-                "className": "WindSpeedFrequencyAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.windDirectionFrequencyAnalyst",
-                "className": "WindDirectionFrequencyAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.windRoseOfTurbine",
-                "className": "WinRoseOfTurbineAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.temperatureLargeComponentsAnalyst",
-                "className": "TemperatureLargeComponentsAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.temperatureEnvironmentAnalyst",
-                "className": "TemperatureEnvironmentAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.generatorSpeedPowerAnalyst",
-                "className": "GeneratorSpeedPowerAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.generatorSpeedTorqueAnalyst",
-                "className": "GeneratorSpeedTorqueAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.pitchPowerAnalyst",
-                "className": "PitchPowerAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.pitchGeneratorSpeedAnalyst",
-                "className": "PitchGeneratorSpeedAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.dataIntegrityOfMinuteAnalyst",
-                "className": "DataIntegrityOfMinuteAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.powerCurveAnalyst",
-                "className": "PowerCurveAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.ratedPowerWindSpeedAnalyst",
-                "className": "RatedPowerWindSpeedAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.ratedWindSpeedAnalyst",
-                "className": "RatedWindSpeedAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.powerScatter2DAnalyst",
-                "className": "PowerScatter2DAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.powerScatterAnalyst",
-                "className": "PowerScatterAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.windSpeedAnalyst",
-                "className": "WindSpeedAnalyst",
-                "methodName": "executeAnalysis"
-            },            
-            {
-                "package": "algorithm.pitchPowerWindSpeedAnalyst",
-                "className": "PitchPowerWindSpeedAnalyst",
-                "methodName": "executeAnalysis"
-            },            
-            {
-                "package": "algorithm.dataIntegrityOfSecondAnalyst",
-                "className": "DataIntegrityOfSecondAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.yawErrorAnalyst",
-                "className": "YawErrorAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.minPitchAnalyst",
-                "className": "MinPitchAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.cpAnalyst",
-                "className": "CpAnalyst",
-                "methodName": "executeAnalysis"
-            },
-			{
-				"package": "algorithm.cpWindSpeedAnalyst",
-				"className": "CpWindSpeedAnalyst",
-				"methodName": "executeAnalysis"
-			},
-            {
-                "package": "algorithm.cpTrendAnalyst",
-                "className": "CpTrendAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.tsrAnalyst",
-                "className": "TSRAnalyst",
-                "methodName": "executeAnalysis"
-            },
-			{
-				"package": "algorithm.tsrWindSpeedAnalyst",
-				"className": "TSRWindSpeedAnalyst",
-				"methodName": "executeAnalysis"
-			},
-            {
-                "package": "algorithm.tsrTrendAnalyst",
-                "className": "TSRTrendAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.powerOscillationAnalyst",
-                "className": "PowerOscillationAnalyst",
-                "methodName": "executeAnalysis"
-            },
-            {
-                "package": "algorithm.pitchTSRCpAnalyst",
-                "className": "PitchTSRCpAnalyst",
-                "methodName": "executeAnalysis"
-            }
-        ]
-    }
-]

+ 0 - 21
dataAnalysisBehavior/behavior/analystExcludeRatedPower.py

@@ -1,21 +0,0 @@
-from .analyst import Analyst
-import os
-import pandas as pd
-import numpy as np
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-
-class AnalystExcludeRatedPower(Analyst):
-    def filterCommon(self,dataFrame:pd.DataFrame, confData:ConfBusiness):
-        dataFrame=super().filterCommon(dataFrame,confData)
-
-        if not self.common.isNone(confData.field_power) and self.node_active_power_max in confData.filter and not self.common.isNone(confData.filter[self.node_active_power_max]) \
-                and not self.common.isNone(confData.field_pitch_angle1) and self.node_angle_pitch_min in confData.filter and not self.common.isNone(confData.filter[self.node_angle_pitch_min]):
-            activePowerMax = float(confData.filter[self.node_active_power_max])
-            anglePitchMin = float(confData.filter[self.node_angle_pitch_min])
-
-            dataFrame = dataFrame[~((dataFrame[confData.field_power] >= activePowerMax*0.9) & (
-                dataFrame[confData.field_power] <= activePowerMax*1.2))]
-        
-        return dataFrame

+ 23 - 4
dataAnalysisBusiness/algorithm/dataIntegrityOfSecondAnalyst.py

@@ -130,17 +130,36 @@ class DataIntegrityOfSecondAnalyst(AnalystNotFilter):
             x_axis_title = "机组"
             y_axis_title = "日期"
 
+        # # 创建热图
+        # fig = go.Figure(data=go.Heatmap(
+        #     z=result.values,
+        #     x=x_labels,
+        #     y=y_labels,
+        #     colorscale='Viridis',
+        #     # colorbar=dict(title='数据完整度%'),
+        #     showscale=False,  # 显示颜色条
+        #     text=result.values,
+        #     texttemplate="%{text}",  # Format the text display inside cells
+        #     # hoverinfo='text'
+        # ))
         # 创建热图
         fig = go.Figure(data=go.Heatmap(
             z=result.values,
             x=x_labels,
             y=y_labels,
-            colorscale='Viridis',
-            # colorbar=dict(title='数据完整度%'),
-            showscale=False,  # 显示颜色条
+            colorscale=[
+                [0.0, 'rgb(255, 102, 102)'],  # 柔和的红色
+                [0.5, 'rgb(255, 102, 102)'],
+                [0.5, 'rgb(255, 255, 153)'],  # 柔和的黄色
+                [0.85, 'rgb(255, 255, 153)'],
+                [0.85, 'rgb(153, 255, 153)'],  # 柔和的绿色
+                [1.0, 'rgb(153, 255, 153)']
+            ],
+            zmin=0,  # 设置颜色范围的最小值
+            zmax=100,  # 设置颜色范围的最大值
+            showscale=True,  # 显示颜色条
             text=result.values,
             texttemplate="%{text}",  # Format the text display inside cells
-            # hoverinfo='text'
         ))
 
         # 更新图形布局

+ 0 - 411
dataAnalysisBusiness/algorithm/dataMarker.py

@@ -1,411 +0,0 @@
-import os
-import re
-import pandas as pd
-import numpy as np
-import matplotlib.pyplot as plt
-from matplotlib.pyplot import MultipleLocator
-import math
-import pdb
-from algorithmContract.confBusiness import *  #将这个包里的全部加载
-
-intervalPower = 25
-intervalWindspeed = 0.25
-
-class DataMarker:
-    
-    #选取时间、风速、功率数据
-    def preprocessData(self,dataFrame:pd.DataFrame,confData:ConfBusiness):
-        timeStamp = dataFrame[confData.field_turbine_time]
-        activePower = dataFrame[confData.field_power]
-        windSpeed = dataFrame[confData.field_wind_speed]
-        dataFramePartOfSCADA = pd.concat([timeStamp,activePower,windSpeed], axis=1)
-        return dataFramePartOfSCADA
-    
-    #计算分仓数目
-    def calculateIntervals(self,activePowerMax, ratedPower, windSpeedCutOut):
-        binNumOfPower = math.floor((activePowerMax) / intervalPower) + 1 if (activePowerMax) >= ratedPower else math.floor(ratedPower / intervalPower)
-        binNumOfWindSpeed = math.ceil(windSpeedCutOut / intervalWindspeed)
-        return binNumOfPower, binNumOfWindSpeed
-
-    def calculateTopP(self,activePowerMax,ratedPower):
-        
-        TopP = 0   
-        if activePowerMax >= ratedPower: 
-            TopP = math.floor((activePowerMax - ratedPower) / intervalPower) + 1  
-        else:  
-            TopP = 0
-        return TopP
-
-    def chooseData(self,dataFramePartOfSCADA,dataFrame:pd.DataFrame,confData:ConfBusiness):
-        
-        # 初始化标签列
-        SM1 = dataFramePartOfSCADA.shape 
-        AA1 = SM1[0]  
-        lab = [[0] for _ in range(AA1)]
-        lab = pd.DataFrame(lab,columns=['lab'])
-        dataFramePartOfSCADA = pd.concat([dataFramePartOfSCADA,lab],axis=1)  #在tpv后加一列标签列
-        dataFramePartOfSCADA = dataFramePartOfSCADA.values
-        SM = dataFramePartOfSCADA.shape #(52561,4)
-        AA = SM[0] 
-        nCounter1 = 0 
-        DzMarch809_0 = np.zeros((AA, 3)) 
-        Point_line = np.zeros(AA, dtype=int)  
-        APower = dataFrame[confData.field_power].values
-        WSpeed = dataFrame[confData.field_wind_speed].values
-
-        for i in range(AA):
-            if (APower[i] > 10.0) & (WSpeed[i] > 0.0):
-                nCounter1 += 1  
-                DzMarch809_0[nCounter1-1, 0] = WSpeed[i]  
-                DzMarch809_0[nCounter1-1, 1] = APower[i] 
-                Point_line[nCounter1-1] = i+1  
-            if APower[i] <= 10: 
-                dataFramePartOfSCADA[i,SM[1]-1] = -1
-                
-            DzMarch809 = DzMarch809_0[:nCounter1, :] 
-            
-        return DzMarch809,nCounter1,dataFramePartOfSCADA,Point_line,SM
-
-    def gridCount(self,binNumOfWindSpeed,binNumOfPower,nCounter1,DzMarch809):  
-        # 遍历有效数据
-        XBoxNumber = np.ones((binNumOfPower, binNumOfWindSpeed),dtype=int) 
-        for i in range(nCounter1):             
-            for m in range(1, binNumOfPower + 1):  
-                if (DzMarch809[i,1] > (m - 1) * intervalPower) and (DzMarch809[i,1] <= m * intervalPower):  
-                    nWhichP = m  
-                    break  
-            for n in range(1, binNumOfWindSpeed + 1):  
-                if (DzMarch809[i, 0] > (n - 1) * intervalWindspeed) and (DzMarch809[i, 0] <= n * intervalWindspeed):  
-                    nWhichV = n  
-                    break  
-            if (nWhichP > 0) and (nWhichV > 0):  
-                XBoxNumber[nWhichP - 1][nWhichV - 1] += 1
-        for m in range(1,binNumOfPower+1):
-            for n in range(1,binNumOfWindSpeed+1):
-                XBoxNumber[m-1,n-1] = XBoxNumber[m-1,n-1] - 1
-        
-        return XBoxNumber
-
-    def percentageDots(self,XBoxNumber, binNumOfPower, binNumOfWindSpeed,axis):
-        
-        BoxPercent = np.zeros((binNumOfPower, binNumOfWindSpeed), dtype=float)     
-        BinSum = np.zeros((binNumOfPower if axis == 'power' else binNumOfWindSpeed, 1), dtype=int)
-        for i in range(1,1+(binNumOfPower if axis == 'power' else binNumOfWindSpeed)):
-            for m in range(1,(binNumOfWindSpeed if axis == 'power' else binNumOfPower)+1):  
-                BinSum[i-1] = BinSum[i-1] + (XBoxNumber[i-1,m-1] if axis == 'power' else XBoxNumber[m-1,i-1])
-            for m in range(1,(binNumOfWindSpeed if axis == 'power' else binNumOfPower)+1):  
-                if BinSum[i-1]>0:
-                    if axis == 'power':
-                        BoxPercent[i-1,m-1] = (XBoxNumber[i-1,m-1] / BinSum[i-1])*100
-                    else:
-                        BoxPercent[m-1,i-1] = (XBoxNumber[m-1,i-1] / BinSum[i-1])*100
-                        
-        return BoxPercent,BinSum
-
-    def maxBoxPercentage(self,BoxPercent, binNumOfPower, binNumOfWindSpeed, axis):
-        
-        BoxMaxIndex = np.zeros((binNumOfPower if axis == 'power' else binNumOfWindSpeed,1),dtype = int) 
-        BoxMax = np.zeros((binNumOfPower if axis == 'power' else binNumOfWindSpeed,1),dtype = float)  
-        for m in range(1,(binNumOfPower if axis == 'power' else binNumOfWindSpeed)+1):
-            BoxMaxIndex[m-1] = (np.argmax(BoxPercent[m-1, :])) if axis == 'power' else (np.argmax(BoxPercent[:, m-1]))
-            BoxMax[m-1] = (np.max(BoxPercent[m-1, :]))if axis == 'power' else (np.max(BoxPercent[:, m-1]))
-
-        return BoxMaxIndex, BoxMax
-
-    def extendBoxPercent(self,m, BoxMax,TopP,BoxMaxIndex,BoxPercent,binNumOfPower,binNumOfWindSpeed):
-        
-        DotDense = np.zeros(binNumOfPower)  
-        DotDenseLeftRight = np.zeros((binNumOfPower,2))
-        DotValve = m 
-        PDotDenseSum = 0
-        for i in range(binNumOfPower - TopP):
-            PDotDenseSum = BoxMax[i] 
-            iSpreadRight = 1  
-            iSpreadLeft = 1         
-            while PDotDenseSum < DotValve:  
-                if (BoxMaxIndex[i] + iSpreadRight) < binNumOfWindSpeed-1-1:  
-                    PDotDenseSum += BoxPercent[i, BoxMaxIndex[i] + iSpreadRight] 
-                    iSpreadRight += 1  
-                else:
-                    break             
-                if (BoxMaxIndex[i] - iSpreadLeft) > 0:  
-                    PDotDenseSum += BoxPercent[i, BoxMaxIndex[i] - iSpreadLeft] 
-                    iSpreadLeft += 1  
-                else:  
-                    break  
-            iSpreadRight = iSpreadRight-1
-            iSpreadLeft = iSpreadLeft-1
-        
-            DotDenseLeftRight[i, 0] = iSpreadLeft 
-            DotDenseLeftRight[i, 1] = iSpreadRight 
-            DotDense[i] = iSpreadLeft + iSpreadRight + 1    
-
-        return DotDenseLeftRight
-
-    def calculatePWidth(self,binNumOfPower,TopP,DotDenseLeftRight,PBinSum):
-        
-
-        PowerLimit = np.zeros(binNumOfPower, dtype=int)  
-        WidthAverage = 0    
-        WidthAverage_L = 0 
-        nCounter = 0  
-        PowerLimitValve = 6    
-        N_Pcount = 20  
-        for i in range(binNumOfPower - TopP):   
-            if (DotDenseLeftRight[i, 1] > PowerLimitValve) and (PBinSum[i] > N_Pcount):  
-                PowerLimit[i] = 1  
-            
-            if DotDenseLeftRight[i, 1] <= PowerLimitValve:  
-                WidthAverage += DotDenseLeftRight[i, 1]
-                WidthAverage_L += DotDenseLeftRight[i,1] 
-                nCounter += 1  
-        WidthAverage /= nCounter if nCounter > 0 else 1  
-        WidthAverage_L /= nCounter if nCounter > 0 else 1   
-
-        return WidthAverage, WidthAverage_L,PowerLimit
-
-    def amendMaxBox(self,binNumOfPower,TopP,PowerLimit,BoxMaxIndex):
-        
-
-        for i in range(1, binNumOfPower - TopP+1):  
-            if (PowerLimit[i] == 1) and (abs(BoxMaxIndex[i] - BoxMaxIndex[i - 1]) > 5):  
-                BoxMaxIndex[i] = BoxMaxIndex[i - 1] + 1  
-
-        return BoxMaxIndex
-
-    def markBoxLimit(self,binNumOfPower,binNumOfWindSpeed,TopP,CurveWidthR,CurveWidthL,BoxMaxIndex):
-        
-        BBoxRemove = np.zeros((binNumOfPower, binNumOfWindSpeed), dtype=int)  
-        for m in range(binNumOfPower - TopP): 
-            for n in range(int(BoxMaxIndex[m]) + int(CurveWidthR), binNumOfWindSpeed):
-                BBoxRemove[m, n] = 1  
-            for n in range(int(BoxMaxIndex[m]) - int(CurveWidthL)+1, 0, -1):   
-                BBoxRemove[m, n-1] = 2 
-        return BBoxRemove
-
-    def markBoxPLimit(self,binNumOfPower,binNumOfWindSpeed,TopP,CurveWidthR,PowerLimit,BoxPercent,BoxMaxIndex,mm_value:int,BBoxRemove,nn_value:int):
-        
-        BBoxLimit = np.zeros((binNumOfPower, binNumOfWindSpeed), dtype=int)  
-        for i in range(2, binNumOfPower - TopP):  
-            if PowerLimit[i] == 1:
-                BBoxLimit[i, int(BoxMaxIndex[i] + CurveWidthR + 1):binNumOfWindSpeed] = 1
-        IsolateValve = 3
-        for m in range(binNumOfPower - TopP):    
-            for n in range(int(BoxMaxIndex[m]) + int(CurveWidthR), binNumOfWindSpeed):    
-                if BoxPercent[m, n] < IsolateValve:   
-                    BBoxRemove[m, n] = 1
-
-        for m in range(binNumOfPower - TopP, binNumOfPower):   
-            for n in range(binNumOfWindSpeed):  
-                BBoxRemove[m, n] = 3
-        
-        # 标记功率主带拐点左侧的欠发网格  
-        for m in range(mm_value - 1, binNumOfPower - TopP): 
-            for n in range(int(nn_value) - 2):
-                BBoxRemove[m, n] = 2
-        
-        return BBoxLimit
-        
-    def markData(self,binNumOfPower, binNumOfWindSpeed,DzMarch809,BBoxRemove,nCounter1):
-        
-        DzMarch809Sel = np.zeros(nCounter1, dtype=int)
-        nWhichP = 0  
-        nWhichV = 0  
-        for i in range(nCounter1):   
-            for m in range( binNumOfPower ):   
-                if ((DzMarch809[i,1])> m * intervalPower) and ((DzMarch809[i,1]) <= (m+1) * intervalPower):  
-                    nWhichP = m  #m记录的是index
-                    break  
-            for n in range( binNumOfWindSpeed ):    
-                if DzMarch809[i,0] > ((n+1) * intervalWindspeed - intervalWindspeed/2) and DzMarch809[i,0] <= ((n+1) * intervalWindspeed + intervalWindspeed / 2):  
-                    nWhichV = n 
-                    break  
-            if nWhichP >= 0 and nWhichV >= 0:  
-                if BBoxRemove[nWhichP, nWhichV] == 1:   
-                    DzMarch809Sel[i] = 1  
-                elif BBoxRemove[nWhichP, nWhichV] == 2:  
-                    DzMarch809Sel[i] = 2  
-                elif BBoxRemove[nWhichP , nWhichV] == 3:  
-                    DzMarch809Sel[i] = 0  
-
-        return DzMarch809Sel
-        
-
-    def windowFilter(self,nCounter1,ratedPower,DzMarch809,DzMarch809Sel,Point_line):
-        
-
-        PVLimit = np.zeros((nCounter1, 3)) 
-        nLimitTotal = 0  
-        nWindowLength = 6  
-        LimitWindow = np.zeros(nWindowLength)
-        UpLimit = 0   
-        LowLimit = 0  
-        PowerStd = 30  
-        nWindowNum = np.floor(nCounter1/nWindowLength)
-        PowerLimitUp = ratedPower - 100  
-        PowerLimitLow = 100  
-
-        # 循环遍历每个窗口  
-        for i in range(int(nWindowNum)):  
-            start_idx = i * nWindowLength  
-            end_idx = start_idx + nWindowLength  
-            LimitWindow = DzMarch809[start_idx:end_idx, 1]  
-            
-            bAllInAreas = np.all(LimitWindow >= PowerLimitLow) and np.all(LimitWindow <= PowerLimitUp)  
-            if not bAllInAreas:  
-                continue  
-            
-            UpLimit = LimitWindow[0] + PowerStd  
-            LowLimit = LimitWindow[0] - PowerStd  
-            
-            bAllInUpLow = np.all(LimitWindow >= LowLimit) and np.all(LimitWindow <= UpLimit)  
-            if bAllInUpLow: 
-                DzMarch809Sel[start_idx:end_idx] = 4  
-    
-                for j in range(nWindowLength):  
-                    PVLimit[nLimitTotal, :2] = DzMarch809[start_idx + j, :2]  
-                    PVLimit[nLimitTotal, 2] = Point_line[start_idx + j]  # 对数据进行标识  
-                    nLimitTotal += 1  
-        return PVLimit,nLimitTotal
-
-    def store_points(self,DzMarch809, DzMarch809Sel,Point_line, nCounter1):  
-          
-        PVDot = np.zeros((nCounter1, 3))
-        PVBad = np.zeros((nCounter1, 3))  
-
-        nCounterPV = 0  
-        nCounterBad = 0 
-        for i in range(nCounter1):
-            if DzMarch809Sel[i] == 0:   
-                nCounterPV += 1 
-                PVDot[nCounterPV-1, :2] = DzMarch809[i, :2]
-                PVDot[nCounterPV-1, 2] = Point_line[i]  
-            elif DzMarch809Sel[i] in [1, 2, 3]:  
-                nCounterBad += 1  
-                PVBad[nCounterBad-1, :2] = DzMarch809[i, :2]  
-                PVBad[nCounterBad-1, 2] = Point_line[i]
-                    
-        return PVDot, nCounterPV,PVBad,nCounterBad  
-
-    def markAllData(self,nCounterPV,nCounterBad,dataFramePartOfSCADA,PVDot,PVBad,SM,nLimitTotal,PVLimit):
-
-        for i in range(nCounterPV):
-            dataFramePartOfSCADA[int(PVDot[i, 2] - 1), (SM[1]-1)] = 1   
-        #坏点  
-        for i in range(nCounterBad):  
-            dataFramePartOfSCADA[int(PVBad[i, 2] - 1),(SM[1]-1)] = 5  # 坏点标识  
-
-        # 对所有数据中的限电点进行标注   
-        for i in range(nLimitTotal):  
-            dataFramePartOfSCADA[int(PVLimit[i, 2] - 1),(SM[1]-1)] = 4  # 限电点标识  
-
-        return dataFramePartOfSCADA
-    
-    # 4. 数据可视化
-    def plotData(self,turbineName:str,ws:list, ap:list):
-        fig = plt.figure()
-        plt.scatter(ws, ap, s=1, c='black', marker='.')
-        ax = plt.gca()
-        ax.xaxis.set_major_locator(MultipleLocator(5))
-        ax.yaxis.set_major_locator(MultipleLocator(500))
-        plt.title(turbineName)
-        plt.xlim((0, 30))
-        plt.ylim((0, 2200))
-        plt.tick_params(labelsize=8)
-        plt.xlabel("V/(m$·$s$^{-1}$)", fontsize=8)
-        plt.ylabel("P/kW", fontsize=8)
-        plt.show()
-    
-    
-    def main(self,confData: ConfBusiness,dataFrame:pd.DataFrame):
-        dataFramePartOfSCADA = self.preprocessData(dataFrame, confData)
-        powerMax = dataFrame[confData.field_power].max()
-
-        binNumOfPower, binNumOfWindSpeed = self.calculateIntervals(powerMax,confData.rated_power,confData.rated_cut_out_windspeed)
-        TopP = self.calculateTopP(powerMax,confData.rated_power)
-        # 根据功率阈值对数据进行标签分配
-        DzMarch809,nCounter1,dataFramePartOfSCADA,Point_line,SM = self.chooseData(dataFramePartOfSCADA,dataFrame,confData)
-        XBoxNumber = self.gridCount(binNumOfWindSpeed,binNumOfPower,nCounter1,DzMarch809)
-        PBoxPercent,PBinSum = self.percentageDots(XBoxNumber, binNumOfPower, binNumOfWindSpeed, 'power')
-        VBoxPercent,VBinSum = self.percentageDots(XBoxNumber, binNumOfPower, binNumOfWindSpeed, 'speed')
-
-        PBoxMaxIndex, PBoxMaxP = self.maxBoxPercentage(PBoxPercent, binNumOfPower, binNumOfWindSpeed, 'power')
-        VBoxMaxIndex, VBoxMaxV = self.maxBoxPercentage(VBoxPercent, binNumOfPower, binNumOfWindSpeed, 'speed')
-        if PBoxMaxIndex[0] > 14: PBoxMaxIndex[0] = 9
-        DotDenseLeftRight = self.extendBoxPercent(90, PBoxMaxP,TopP,PBoxMaxIndex,PBoxPercent,binNumOfPower,binNumOfWindSpeed)
-        WidthAverage, WidthAverage_L,PowerLimit = self.calculatePWidth(binNumOfPower,TopP,DotDenseLeftRight,PBinSum)
-        PBoxMaxIndex = self.amendMaxBox(binNumOfPower,TopP,PowerLimit,PBoxMaxIndex)
-        # 计算功率主带的左右边界  
-        CurveWidthR = np.ceil(WidthAverage) + 2  
-        CurveWidthL = np.ceil(WidthAverage_L) + 2 
-        #确定功率主带的左上拐点,即额定风速位置的网格索引
-        CurveTop = np.zeros((2, 1), dtype=int)  
-        BTopFind = 0  
-        mm_value = None
-        nn_value = None
-        for m in range(binNumOfPower - TopP, 0, -1):
-            for n in range(int(np.floor(int(confData.rated_cut_in_windspeed) / intervalWindspeed)), binNumOfWindSpeed - 1):   
-                if (VBoxPercent[m, n - 1] < VBoxPercent[m, n]) and (VBoxPercent[m, n] <= VBoxPercent[m, n + 1]) and (XBoxNumber[m, n] >= 3):   
-                    CurveTop[0] = m  
-                    CurveTop[1] = n  #[第80个,第40个]
-                    BTopFind = 1
-                    mm_value = m
-                    nn_value = n
-                    break 
-            if BTopFind == 1:  
-                break 
-        #标记网格
-        BBoxRemove = self.markBoxLimit(binNumOfPower,binNumOfWindSpeed,TopP,CurveWidthR,CurveWidthL,PBoxMaxIndex)
-        if mm_value is not None and nn_value is not None:
-            BBoxLimit = self.markBoxPLimit(binNumOfPower,binNumOfWindSpeed,TopP,CurveWidthR,PowerLimit,PBoxPercent,PBoxMaxIndex,mm_value,BBoxRemove,nn_value)
-        DzMarch809Sel = self.markData(binNumOfPower, binNumOfWindSpeed,DzMarch809,BBoxRemove,nCounter1)
-        PVLimit,nLimitTotal = self.windowFilter(nCounter1,confData.rated_power,DzMarch809,DzMarch809Sel,Point_line)
-        #将功率滑动窗口主带平滑化
-        nSmooth = 0   
-        for i in range(binNumOfPower - TopP - 1):  
-            PVLeftDown = np.zeros(2)  
-            PVRightUp = np.zeros(2)   
-            if PBoxMaxIndex[i + 1] - PBoxMaxIndex[i] >= 1:  
-                # 计算左下和右上顶点的坐标  
-                PVLeftDown[0] = (PBoxMaxIndex[i]+1 + CurveWidthR) * 0.25 - 0.125  
-                PVLeftDown[1] = (i) * 25  
-                PVRightUp[0] = (PBoxMaxIndex[i+1]+1 + CurveWidthR) * 0.25 - 0.125  
-                PVRightUp[1] = (i+1) * 25  
-                    
-                for m in range(nCounter1):  
-                    # 检查当前点是否在锯齿区域内  
-                    if (DzMarch809[m, 0] > PVLeftDown[0]) and (DzMarch809[m, 0] < PVRightUp[0]) and (DzMarch809[m, 1] > PVLeftDown[1]) and (DzMarch809[m, 1] < PVRightUp[1]):
-                        # 检查斜率是否大于对角连线  
-                        if ((DzMarch809[m, 1] - PVLeftDown[1]) / (DzMarch809[m, 0] - PVLeftDown[0])) > ((PVRightUp[1] - PVLeftDown[1]) / (PVRightUp[0] - PVLeftDown[0])):
-                            # 如果在锯齿左上三角形中,则选中并增加锯齿平滑计数器  
-                            DzMarch809Sel[m] = 0  
-                            nSmooth += 1  
-        # DzMarch809Sel 数组现在包含了锯齿平滑的选择结果,nSmooth 是选中的点数
-        PVDot, nCounterPV,PVBad,nCounterBad = self.store_points(DzMarch809, DzMarch809Sel,Point_line, nCounter1)
-        #标注   
-        dataFramePartOfSCADA = self.markAllData(nCounterPV,nCounterBad,dataFramePartOfSCADA,PVDot,PVBad,SM,nLimitTotal,PVLimit)
-        A = dataFramePartOfSCADA[:,-1]
-        A=pd.DataFrame(A,columns=['lab'])
-
-        dataFrame = pd.concat([dataFrame,A],axis=1) 
-
-        """
-        标识	说明
-        5	坏点
-        4	限功率点
-        1	好点
-        0	null
-        -1	P<=10
-        """
-        print("lab unique :",dataFrame['lab'].unique())
-        # data=dataFrame[dataFrame['lab']==1]        
-        # self.plotData(data[Field_NameOfTurbine].iloc[0],data[confData.field_wind_speed],data[confData.field_power])
-
-        return dataFrame
-        
-
-    if __name__ == '__main__':
-        main()
-
-
-

+ 105 - 3
dataAnalysisBusiness/algorithm/pitchPowerAnalyst.py

@@ -35,7 +35,35 @@ class PitchPowerAnalyst(AnalystWithGoodBadPoint):
 
         dataFrameMerge = dataFrame[(dataFrame[Field_ActiverPower] > 0)].sort_values(by=Field_YearMonth)
         grouped = dataFrameMerge.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
-
+                # 定义固定的颜色映射列表
+        fixed_colors = [
+                "#3E409C",
+                "#476CB9",
+                "#3586BF",
+                "#4FA4B5",
+                "#52A3AE",
+                "#60C5A3",
+                "#85D0AE",
+                "#A8DCA2",
+                "#CFEE9E",
+                "#E4F39E",
+                "#EEF9A7",
+                "#FBFFBE",
+                "#FDF1A9",
+                "#FFE286",
+                "#FFC475",
+                "#FCB06C",
+                "#F78F4F",
+                "#F96F4A",
+                "#E4574C",
+                "#CA3756",
+                "#AF254F"
+        ]
+
+        # 将 fixed_colors 转换为 Plotly 的 colorscale 格式
+        fixed_colorscale = [
+            [i / (len(fixed_colors) - 1), color] for i, color in enumerate(fixed_colors)
+        ]
         # 遍历每个设备并绘制散点图
         result_rows1 = []
         for name, group in grouped:
@@ -50,7 +78,7 @@ class PitchPowerAnalyst(AnalystWithGoodBadPoint):
                 # marker=dict(color='blue', size=3.5)
                 marker=dict(
                     color=group[Field_UnixYearMonth],
-                    colorscale='Rainbow',
+                    colorscale=fixed_colorscale,
                     size=3,
                     opacity=0.7,
                     colorbar=dict(
@@ -133,7 +161,7 @@ class PitchPowerAnalyst(AnalystWithGoodBadPoint):
             by=Field_YearMonth)
 
         grouped = dataFrame.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
-
+        '''
         # 遍历每个设备的数据
         result_rows2 = []
         for name, group in grouped:
@@ -190,6 +218,80 @@ class PitchPowerAnalyst(AnalystWithGoodBadPoint):
                         itemwidth=80  # Set the width of legend items to 50 pixels
                     )
                 )
+        '''
+        # 假设 colorsList 已经在代码的其他部分定义
+        colorsList = [  
+            "#3E409C",
+            "#3586BF",
+            "#52A3AE",
+            "#85D0AE",
+            "#A8DCA2",
+            "#FBFFBE",
+            "#FDF1A9",
+            "#FFE286",
+            "#FCB06C",
+            "#F96F4A",
+            "#E4574C",
+            "#AF254F"
+        ]
+        
+        # 遍历每个设备的数据
+        result_rows2 = []
+        for name, group in grouped:
+            if len(group[Field_YearMonth].unique()) > 1:
+                fig = px.scatter_3d(
+                    group,
+                    x=Field_PitchAngel1,
+                    y=Field_YearMonth,
+                    z=Field_ActiverPower,
+                    color=Field_YearMonth,
+                    color_discrete_sequence=colorsList,  # 使用 colorsList 作为颜色映射
+                    labels={
+                        Field_PitchAngel1: '桨距角',
+                        Field_YearMonth: '时间',
+                        Field_ActiverPower: '功率'
+                    },
+                )
+        
+                # 设置固定散点大小
+                fig.update_traces(marker=dict(size=1.5))
+        
+                # 更新图形的布局
+                fig.update_layout(
+                    title={
+                        "text": f'月度桨距角功率3D散点图: {name[0]}',
+                        "x": 0.5
+                    },
+                    scene=dict(
+                        xaxis=dict(
+                            title='桨距角',
+                            dtick=self.axisStepPitchAngle,
+                            range=[self.axisLowerLimitPitchAngle, self.axisUpperLimitPitchAngle],
+                        ),
+                        yaxis=dict(
+                            title='时间',
+                            tickformat='%Y-%m',  # 日期格式,
+                            dtick='M1',  # 每月一个刻度
+                            showgrid=True,  # 显示网格线
+                        ),
+                        zaxis=dict(
+                            title='功率',
+                            dtick=self.axisStepActivePower,
+                            range=[self.axisLowerLimitActivePower, self.axisUpperLimitActivePower],
+                            showgrid=True,  # 显示网格线
+                        )
+                    ),
+                    scene_camera=dict(
+                        up=dict(x=0, y=0, z=1),  # 保持相机向上方向不变
+                        center=dict(x=0, y=0, z=0),  # 保持相机中心位置不变
+                        eye=dict(x=-1.8, y=-1.8, z=1.2)  # 调整eye属性以实现水平旋转180°
+                    ),
+                    legend=dict(
+                        orientation="h",
+                        itemsizing="constant",  # Use constant size for legend items
+                        itemwidth=80  # Set the width of legend items to 50 pixels
+                    )
+                )
 
                 # 保存图像
                 filePathOfHtml = os.path.join(

+ 97 - 1
dataAnalysisBusiness/algorithm/powerScatter2DAnalyst.py

@@ -59,7 +59,7 @@ class PowerScatter2DAnalyst(AnalystWithGoodBadPoint):
         #     cut_in_ws = dataFrame[Field_CutInWS].min() - 1
         # else:
         #     cut_in_ws = 2
-
+        '''
         # 按设备名分组数据
         grouped = dataFrame.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
         result_rows = []
@@ -122,6 +122,102 @@ class PowerScatter2DAnalyst(AnalystWithGoodBadPoint):
                                          name=f'{datetime.fromtimestamp(month_data["monthIntTime"].iloc[0]).strftime("%Y-%m")}',
                                          showlegend=True)
                     fig.add_trace(scatter)
+        '''
+        
+        # 按设备名分组数据
+        grouped = dataFrame.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
+        result_rows = []
+
+        # 定义固定的颜色映射列表
+        fixed_colors = [
+                "#3E409C",
+                "#476CB9",
+                "#3586BF",
+                "#4FA4B5",
+                "#52A3AE",
+                "#60C5A3",
+                "#85D0AE",
+                "#A8DCA2",
+                "#CFEE9E",
+                "#E4F39E",
+                "#EEF9A7",
+                "#FBFFBE",
+                "#FDF1A9",
+                "#FFE286",
+                "#FFC475",
+                "#FCB06C",
+                "#F78F4F",
+                "#F96F4A",
+                "#E4574C",
+                "#CA3756",
+                "#AF254F"
+        ]
+
+        # 将 fixed_colors 转换为 Plotly 的 colorscale 格式
+        fixed_colorscale = [
+            [i / (len(fixed_colors) - 1), color] for i, color in enumerate(fixed_colors)
+        ]
+        fixed_colors_points = [
+            "#F96F4A",
+            "#FFC475",
+            "#FBFFBE",
+            "#85D0AE",
+            "#3586BF",
+            "#3E409C"
+
+        ]
+        # 遍历每个设备的数据
+        for name, group in grouped:
+            fig = make_subplots()
+
+            # 提取月份
+            group['month'] = group['monthIntTime'].apply(lambda x: datetime.fromtimestamp(x).month)
+            unique_months = group['month'].unique()
+
+            # 计算时间跨度
+            time_span_months = len(unique_months)
+
+            if time_span_months >= 6:
+                # 绘制散点图(时间跨度大于等于6个月)
+                scatter = go.Scatter(x=group[Field_WindSpeed],
+                                     y=group[Field_ActiverPower],
+                                     mode='markers',
+                                     marker=dict(
+                                         color=group['monthIntTime'],
+                                         colorscale=fixed_colorscale,  # 使用自定义的 colorscale
+                                         size=3,
+                                         opacity=0.7,
+                                         colorbar=dict(
+                                             tickvals=np.linspace(
+                                                 group['monthIntTime'].min(), group['monthIntTime'].max(), 6),
+                                             ticktext=[datetime.fromtimestamp(ts).strftime(
+                                                 '%Y-%m') for ts in np.linspace(group['monthIntTime'].min(), group['monthIntTime'].max(), 6)],
+                                             thickness=18,
+                                             len=1,  # 设置颜色条的长度,使其占据整个图的高度
+                                             outlinecolor='rgba(255,255,255,0)'
+                                         ),
+                                         showscale=True
+                                     ),
+                                     showlegend=False)  # 不显示散点图的 legend,用 colorbar 代替
+                fig.add_trace(scatter)
+            else:
+                # 绘制散点图(时间跨度小于6个月)
+                for i, month in enumerate(unique_months):
+                    month_data = group[group['month'] == month]
+                    # 使用固定的颜色列表
+                    color = fixed_colors_points[i % len(fixed_colors_points)]
+                    scatter = go.Scatter(x=month_data[Field_WindSpeed],
+                                         y=month_data[Field_ActiverPower],
+                                         mode='markers',
+                                         marker=dict(
+                                             color=color,
+                                             size=3,
+                                             opacity=0.7
+                                         ),
+                                         name=f'{datetime.fromtimestamp(month_data["monthIntTime"].iloc[0]).strftime("%Y-%m")}',
+                                         showlegend=True)
+                    fig.add_trace(scatter)
+
            # 绘制合同功率曲线
             line = go.Scatter(x=dataFrameGuaranteePowerCurve[Field_WindSpeed],
                               y=dataFrameGuaranteePowerCurve[Field_ActiverPower],

+ 16 - 3
dataAnalysisBusiness/algorithm/powerScatterAnalyst.py

@@ -29,7 +29,7 @@ class PowerScatterAnalyst(AnalystWithGoodBadPoint):
             print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
             return
 
-        # # dataFrameGuaranteePowerCurve=self.powerFarmInfo[Field_PowerContractURL]
+        # dataFrameGuaranteePowerCurve=self.powerFarmInfo[Field_PowerContractURL]
         # grouped=self.dataFrameContractOfTurbine.groupby([Field_PowerFarmCode, Field_MillTypeCode])
 
         # for groupByKey,contractPowerCurveOfMillType in grouped:
@@ -55,8 +55,21 @@ class PowerScatterAnalyst(AnalystWithGoodBadPoint):
         conf (ConfBusiness): 配置
         """
         # 按设备名分组数据
-        colorsList = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
-                      '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf', '#aec7e8', '#ffbb78']
+        # colorsList = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
+        #               '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf', '#aec7e8', '#ffbb78']
+        colorsList = [  
+                        "#3E409C",
+                        "#3586BF",
+                        "#52A3AE",
+                        "#85D0AE",
+                        "#A8DCA2",
+                        "#FBFFBE",
+                        "#FDF1A9",
+                        "#FFE286",
+                        "#FCB06C",
+                        "#F96F4A",
+                        "#E4574C",
+                        "#AF254F"]
         cutInWsField = self.turbineModelInfo[Field_CutInWS]
         cut_in_ws = cutInWsField.min() - 1 if cutInWsField.notna().any() else 2
         # if not dataFrame.empty and Field_CutInWS in  dataFrame.columns and  dataFrame[Field_CutInWS].notna().any():

+ 8 - 6
dataAnalysisBusiness/algorithm/productionIndicatorAnalyst.py

@@ -83,7 +83,8 @@ class ProductionIndicatorAnalyst(AnalystNotFilter):
         # 平均风速
         WindSpeedAvr = df[Field_WindSpeed].mean()
         # 切入风速
-        cut_in_ws = self.turbineModelInfo[Field_CutInWS]
+        CutInWS = self.turbineModelInfo[Field_CutInWS]
+        cut_in_ws=CutInWS.iloc[0]
         # 风机可利用率
         nShouldGP = (df[Field_WindSpeed] >= cut_in_ws).sum()
         nRealGP = ((df[Field_WindSpeed] >= cut_in_ws) & (df[Field_ActiverPower] > 0)).sum()
@@ -229,7 +230,8 @@ class ProductionIndicatorAnalyst(AnalystNotFilter):
         '''
         # 确定新的风速范围
         # 下限:切入风速减1m/s
-        cut_in_ws = self.turbineModelInfo[Field_CutInWS]
+        cut_in_ws = self.turbineModelInfo[Field_CutInWS].iloc[0]
+
         lower_limit = cut_in_ws - 1.0
         # 找到对应85%额定功率的风速
         df_ideal_sorted = dataFrameGuaranteePowerCurve.sort_values(by=Field_WindSpeed)
@@ -309,10 +311,10 @@ class ProductionIndicatorAnalyst(AnalystNotFilter):
         Qp, Thc, Qdr,Rdr = self.Production_indicators(dataFrameresults)
         
         # 将Qp, Thc, Rdr添加到results_df中
-        dataFrameResult_total['Qp'] = [Qp]
-        dataFrameResult_total['Thc'] = [Thc]
-        dataFrameResult_total['Rdr'] = [Rdr]
-        dataFrameResult_total['Qdr'] = [Qdr]
+        dataFrameResult_total['Qp'] = [Qp]#风场总发电量
+        dataFrameResult_total['Thc'] = [Thc]#风场等效利用小时
+        dataFrameResult_total['Rdr'] = [Rdr]#风场弃风率
+        dataFrameResult_total['Qdr'] = [Qdr]#风场弃风电量
                 #保存为csv文件
 
         print("dataFrameResult_total:",dataFrameResult_total)

+ 24 - 6
dataAnalysisBusiness/algorithm/temperatureEnvironmentAnalyst.py

@@ -12,7 +12,7 @@ from plotly.subplots import make_subplots
 
 class TemperatureEnvironmentAnalyst(AnalystWithGoodBadLimitPoint):
     """
-    风电机组大部件温升分析
+    风电机组环境温度传感器分析
     """
 
     def typeAnalyst(self):
@@ -33,8 +33,25 @@ class TemperatureEnvironmentAnalyst(AnalystWithGoodBadLimitPoint):
             {Field_EnvTemp: 'median'})
         turbineEnvTempData = turbineEnvTempData.reset_index()
         mergeData = self.mergeData(self.turbineInfo, turbineEnvTempData)
-
-        return self.draw(mergeData, outputAnalysisDir, conf)
+        # 分机型
+        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)]
+            returnData= self.draw(mergeData, outputAnalysisDir, conf,currTurbineModeInfo)
+            returnDatas.append(returnData)
+
+        returnResult = pd.concat(returnDatas, ignore_index=True) 
+        return returnResult
+        # return self.draw(mergeData, outputAnalysisDir, conf)
 
     def mergeData(self,  turbineInfos: pd.DataFrame, turbineEnvTempData):
         """
@@ -80,7 +97,8 @@ class TemperatureEnvironmentAnalyst(AnalystWithGoodBadLimitPoint):
 
     fieldTemperatureDiff = "temperature_diff"
 
-    def draw(self, dataFrame: pd.DataFrame, outputAnalysisDir, conf: Contract, charset=charset_unify):
+    # def draw(self, dataFrame: pd.DataFrame, outputAnalysisDir, conf: Contract, charset=charset_unify):
+    def draw(self, dataFrame: pd.DataFrame, outputAnalysisDir, conf: Contract, turbineModelInfo: pd.Series):
         # 处理数据
         dataFrame['new'] = dataFrame.loc[:, [Field_NameOfTurbine,
                                              Field_Longitude, Field_Latitude, Field_EnvTemp]].apply(tuple, axis=1)
@@ -114,7 +132,7 @@ class TemperatureEnvironmentAnalyst(AnalystWithGoodBadLimitPoint):
             row=1, col=1
         )
         fig1.update_layout(
-            title={'text': f'温度偏差-{self.turbineModelInfo[Field_MachineTypeCode].iloc[0]}', 'x': 0.5},
+            title={'text': f'温度偏差-{turbineModelInfo[Field_MachineTypeCode]}', 'x': 0.5},
             xaxis_title='机组名称',
             yaxis_title='温度偏差',
             shapes=[
@@ -165,7 +183,7 @@ class TemperatureEnvironmentAnalyst(AnalystWithGoodBadLimitPoint):
             row=1, col=1
         )
         fig2.update_layout(
-            title={'text': f'平均温度-{self.turbineModelInfo[Field_MachineTypeCode].iloc[0]}', 'x': 0.5},
+            title={'text': f'平均温度-{turbineModelInfo[Field_MachineTypeCode]}', 'x': 0.5},
             xaxis_title='机组名称',
             yaxis_title=' 温度',
             xaxis=dict(tickangle=-45)  # 为x轴也设置旋转角度

+ 0 - 81
dataAnalysisBusiness/algorithm/windRoseOfTurbine.py

@@ -1,81 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import plotly.graph_objects as go
-from plotly.subplots import make_subplots
-import seaborn as sns
-import matplotlib.pyplot as plt
-from matplotlib.ticker import MultipleLocator
-from windrose import WindroseAxes
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-from matplotlib.cm import get_cmap 
-from matplotlib.colors import ListedColormap   
-
-
-class WinRoseOfTurbineAnalyst(Analyst):
-    """
-    风电机组变桨-功率分析
-    """
-
-    def typeAnalyst(self):
-        return "wind_rose_turbine"
-
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.windRoseAnalysis(dataFrameMerge, outputAnalysisDir, confData)
-
-    def windRoseAnalysis(self, dataFrameMerge:pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # 检查所需列是否存在
-        required_columns = {confData.field_wind_dir,confData.field_wind_speed}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-        
-        # 风速区间  
-        bins = [0, 3, 6, 9, np.inf]  
-        speed_labels = ['[0,3)', '[3,6)', '[6,9)', '>=9']  
-        # 准备颜色映射  
-        colors = plt.cm.Blues(np.linspace(0, 1, len(speed_labels)))  
-        cmap = ListedColormap(colors)  
-        # 将风向按照22.5度一个间隔进行分组  
-        wind_directions = np.arange(0, 360, 22.5)  
-
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-        print("self.ratedPower {}".format(confData.rated_power))
-        # 遍历每个设备并绘制图
-        for name, group in grouped:            
-            # 对风速进行分箱处理,但不添加到DataFrame中  
-            speed_bins = pd.cut(group[confData.field_wind_speed], bins=bins, labels=speed_labels)  
-            
-            # 将风向按照22.5度一个间隔进行分组  
-            wind_directions = np.arange(0, 360, 22.5)  
-            group['风向分组'] = pd.cut(group[confData.field_wind_dir], bins=wind_directions, labels=wind_directions[:-1])  
-            
-            # 绘制风玫瑰图  
-            fig, ax = plt.subplots(figsize=(8, 8), subplot_kw={'polar': True})  
-            
-            # 为每个风速区间绘制风向的条形图,并添加图例  
-            for i, (label, color) in enumerate(zip(speed_labels, colors)):  
-                # 筛选出当前风速区间的数据  
-                subset = group[speed_bins == label]  
-                # 计算每个风向分组的频数  
-                counts = subset['风向分组'].value_counts().reindex(wind_directions[:-1], fill_value=0)  
-                # 绘制条形图,并添加标签用于图例  
-                bar = ax.bar(counts.index * np.pi / 180, counts.values, color=cmap(i), alpha=0.75, width=(22.5 * np.pi / 180), label=label)  
-            
-            # 设置标题和标签  
-            ax.set_title(f"Wind Rose {name}", va='top')  
-            ax.set_theta_zero_location('N')  # 设置0度位置为北  
-            ax.set_theta_direction(-1)  # 设置角度方向为顺时针  
-            ax.set_yticklabels([])  # 不显示y轴刻度标签  
-            ax.set_xticks(wind_directions * np.pi / 180)  # 设置x轴刻度  
-            ax.set_xticklabels(['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'])  # 设置x轴刻度标签为方向  
-            
-            # 添加图例,并设置位置以避免与图形重叠  
-            ax.legend(title='Wind Speed', bbox_to_anchor=(1.1, 1), loc='center left', borderaxespad=0.)   
-
-            # 保存图像并关闭绘图窗口
-            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            plt.savefig(output_file, bbox_inches='tight', dpi=120)
-            plt.close()

+ 120 - 202
dataAnalysisBusiness/algorithm/yawErrorAnalyst.py

@@ -9,7 +9,6 @@ from plotly.subplots import make_subplots
 from scipy.optimize import curve_fit
 
 
-
 class YawErrorAnalyst(AnalystWithGoodPoint):
     """
     风电机组静态偏航误差分析
@@ -20,230 +19,167 @@ class YawErrorAnalyst(AnalystWithGoodPoint):
     fieldPowerMax = 'max_power'
     fieldPowerMin = 'min_power'
     fieldPowerMedian = 'median_power'
-    fieldYawError = 'yaw_error'
-    fieldStep = 'wsstep'
-    fieldK = 'k' 
-    fieldWindSpeed = 'mean_WindSpeed'
-    fieldcount = 'point_num'
+    fieldPowerGT0p = 'power_gt_0'
+    fieldPowerGT80p = 'power_gt_80p'
+    fieldPowerRatio0p = 'ratio_0'
+    fieldPowerRatio80p = 'ratio_80p'
+    fieldSlop = 'slop'
 
     def typeAnalyst(self):
         return "yaw_error"
 
     def selectColumns(self):
-        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 [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 dataFrame
 
-    def calculateYawError(self, dataFrame: pd.DataFrame, fieldAngleInclude,fieldActivePower, fieldWindSpeed):
-        
+    def calculateYawError(self, dataFrame: pd.DataFrame, fieldAngleInclude, fieldActivePower):
         dataFrame = dataFrame.dropna(
-            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, :]))
+            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]
 
-        # 将结果转换为 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)
+        # Calculate total sums for conditions
+        power_gt_0_sum = grouped[self.fieldPowerGT0p].sum()
+        power_gt_80p_sum = grouped[self.fieldPowerGT80p].sum()
 
-        final_df = pd.concat([final_df, pd.DataFrame(rows)], ignore_index=True)
+        # 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]
 
-        return result_df,final_df
+        # Sort by wind direction floor
+        grouped.sort_values(self.fieldWindDirFloor, inplace=True)
 
+        # # Write to CSV
+        # grouped.to_csv(output_path, index=False)
+
+        return grouped
+
+    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)
-        # dataFrameMerge.to_csv('D:\dashengkeji\project\WTOAAM\debug_tset_20241008\dataFrameJHS_WOG00935.csv',index=False)
         return self.yawErrorAnalysis(dataFrameMerge, outputAnalysisDir, conf)
 
     def yawErrorAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract):
         # 检查所需列是否存在
-        required_columns = {Field_ActiverPower, Field_YawError,Field_WindSpeed,Field_PitchAngel1,Field_Cp,Field_TSR}
+        required_columns = {Field_ActiverPower, Field_YawError}
         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:
-            group = self.filterCommon(group,conf)
-            df,final_df = self.calculateYawError(
-                group, Field_YawError, Field_ActiverPower,Field_WindSpeed)
-            
+            df = self.calculateYawError(
+                group, Field_YawError, Field_ActiverPower)
+
             df.dropna(inplace=True)
-            
-            # 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
-            })
+            # drop extreme value, the 3 largest and 3 smallest
 
-            #对 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
-                ))
-            
+            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
+            )
+
+            # Update layout
             fig.update_layout(
-                title=f'偏航误差分析 {name[0]} ',
-                xaxis_title='偏航角度',
-                yaxis_title='平均功率',
-                legend_title='风速区间',
-                showlegend=True
+                title={
+                    "text": f"Yaw Error Analysis: {name[0]}",
+                    "x": 0.5
+                },
+                showlegend=True,
+                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
             )
-            
+
             # 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)
+
             result_rows.append({
                 Field_Return_TypeAnalyst: self.typeAnalyst(),
                 Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
@@ -260,24 +196,6 @@ class YawErrorAnalyst(AnalystWithGoodPoint):
                 Field_CodeOfTurbine: name[1],
                 Field_Return_FilePath: filePathOfHtml,
                 Field_Return_IsSaveDatabase: False
-            
-            
-            })
-            # 初始化一个空的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,指定列名

+ 24 - 21
dataAnalysisBusiness/algorithm/yawErrorDensityAnalyst.py

@@ -24,13 +24,13 @@ class YawErrorDensityAnalyst(AnalystWithGoodPoint):
         dataFrame = dataFrame.dropna(
             subset=[Field_NameOfTurbine, Field_YawError, Field_ActiverPower,Field_WindSpeed])
 
-        filtered_dataFrame = dataFrame[dataFrame[Field_YawError].abs() <= 30]
+        filtered_dataFrame = dataFrame[(dataFrame[Field_YawError].abs() <= 30)&(dataFrame[Field_WindSpeed] >= 0)&(dataFrame[Field_WindSpeed] <= 25)]
         x=filtered_dataFrame[Field_YawError]
         y=filtered_dataFrame[Field_WindSpeed]
         # data = np.column_stack((x, y))  # 合并为两列数组
         # 使用 binned_statistic_2d 来计算散点的密度分布
-        binSize_x = 40
-        binSize_y = 40
+        binSize_x = 60
+        binSize_y = 50
         counts, x_edges, y_edges, binnumber = binned_statistic_2d(x, y, values=None, statistic='count', bins=[binSize_x, binSize_y])
 
         # 将数据密度转化为颜色值
@@ -130,23 +130,26 @@ class YawErrorDensityAnalyst(AnalystWithGoodPoint):
             print(f'{name[0]}指标:',result)
             # 用密度作为颜色绘制散点图,并限制横坐标范围为 -20 到 20
             fig = go.Figure()
-            fig.add_trace(go.Scatter(
-                                    x=df["x"],
-                                    y=df["y"],
-                                    mode='markers', 
-                                    marker=dict(size=3,
-                                                opacity=0.7, 
-                                                color=df["density"], 
-                                                colorscale=fixed_colorscale, 
-                                                showscale=True,
-                                                )
-                                        ))
-            fig.update_layout(xaxis_title='对风角度',
-                               yaxis_title='风速', 
-                               title=f' 动态偏航误差分析-{name[0]}',
-                               xaxis=dict(range=[-30, 30]),
-                               yaxis=dict(range=[0, 25]))
-
+            fig.add_trace(go.Scattergl(
+                x=df["x"],
+                y=df["y"],
+                mode='markers', 
+                marker=dict(
+                    size=3,
+                    opacity=0.7, 
+                    color=df["density"], 
+                    colorscale=fixed_colorscale, 
+                    showscale=True,
+                )
+            ))
+
+            fig.update_layout(
+                xaxis_title='对风角度',
+                yaxis_title='风速', 
+                title=f'动态偏航误差分析-{name[0]}',
+                xaxis=dict(range=[-20, 20]),  # 限制横坐标范围为 -20 到 20
+                yaxis=dict(range=[0, 25])
+            )
             # Save to file
             filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
             fig.write_image(filePathOfImage, scale=3)
@@ -168,7 +171,7 @@ class YawErrorDensityAnalyst(AnalystWithGoodPoint):
                 Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
                 Field_CodeOfTurbine: name[1],
                 Field_Return_FilePath: filePathOfHtml,
-                Field_Return_IsSaveDatabase: False
+                Field_Return_IsSaveDatabase: True
             
             
             })

+ 0 - 4
dataContract/__init__.py

@@ -1,4 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from . import algorithmContract
-__all__=['algorithmContract']

+ 0 - 88
dataContract/algorithmContract/dataContract.json

@@ -1,88 +0,0 @@
-{
-	"dataContractType": {
-		"dataContractType": "analysisExecuteOrder",
-		"version": "1.0.0"
-	},
-	"dataContract": {
-		"dataSource": {
-			"scada": "minute"
-		},
-		"dataFilter": {
-			"powerFarmID": "",
-			"turbines": [
-				{
-					"dataBatchNum": "B2024042211-0"
-				},
-				{
-					"dataBatchNum": "B2024042211-0"
-				},
-				{
-					"dataBatchNum": "B2024042211-1"
-				}
-			],
-			"beginTime": "2023-01-01 00:00:00",
-			"endTime": "2023-12-31 23:59:59",
-			"excludingMonths": [
-				"2023-12",
-				"2023-09"
-			],
-			"customFilter": {
-				"valueWindSpeed": {
-					"min": 3.0,
-					"max": 25.0
-				},
-				"valuePitchAngle": {
-					"min": 2,
-					"max": null
-				},
-				"valueActivePower": {
-					"min": 10,
-					"max": 2500
-				},
-                "valueGeneratorSpeed": {
-                    "min": 10,
-                    "max": 2500
-                }
-			}
-		},
-		"configAnalysis": [
-            {
-                "package": "algorithm.windSpeedFrequencyAnalyst",
-                "className": "WindSpeedFrequencyAnalyst",
-                "methodName": "executeAnalysis"
-            },
-			{
-				"package": "algorithm.generatorSpeedPowerAnalyst",
-				"className": "GeneratorSpeedPowerAnalyst",
-				"methodName": "executeAnalysis"
-			}
-		],
-		"graphSets": {
-			"generatorSpeed": {
-				"step": 200,
-				"min": 1000,
-				"max": 2000
-			},
-			"generatorTorque": {
-				"step": 2000,
-				"min": 0,
-				"max": 12000
-			},
-			"cp": {
-				"step": 0.5,
-				"min": 0,
-				"max": 2
-			},
-			"tsr": {
-				"step": 5,
-				"min": 0,
-				"max": 30
-			},
-			"pitchAngle": {
-				"step": 1,
-				"min": -1,
-				"max": 20
-			}
-		}
-	}
-}

+ 0 - 0
mydatabase.db


+ 0 - 91
wtoaamapi/apps/business/main.py

@@ -1,91 +0,0 @@
-import pandas as pd
-import importlib
-import concurrent.futures
-from utils.jsonUtil import JsonUtil
-from algorithmContract.confBusiness import ConfBusiness
-from algorithm.dataProcessor import DataProcessor
-from behavior.baseAnalyst import BaseAnalyst
-from behavior.analyst import Analyst
-import seaborn as sns
-sns.set_style("darkgrid") 
-
-def buildDynamicInstance(module_path, class_name, confData: ConfBusiness):
-    # 动态导入模块
-    module = importlib.import_module(module_path)
-    # 获取类
-    cls = getattr(module, class_name)
-    # 创建类实例
-    instance = cls(confData)
-
-    return instance
-
-def dynamic_instance_and_call(module_path, class_name, method_name, *args, **kwargs):
-    # 动态导入模块
-    module = importlib.import_module(module_path)
-    # 获取类
-    cls = getattr(module, class_name)
-    # 创建类实例
-    instance = cls()
-    # 获取方法
-    method = getattr(instance, method_name)
-    # 调用方法
-    method(*args, **kwargs)
-
-
-def loadJson(filePath):
-    # 打开并读取JSON文件
-    with open(filePath, 'r') as f:
-        data = JsonUtil.read_json(filePath)
-
-    return data
-
-def executeAnalysis(config):
-    configBusinessFilePath=config["configFilePath"]
-    print(configBusinessFilePath)
-    businessConfig=ConfBusiness()
-    configBusiness = businessConfig.loadConfig(configBusinessFilePath)
-
-    baseAnalysts=[]
-    noCustomFilterAnalysts = []
-    analysts = []
-
-    for dynamicAnalyst in config["configAnalysis"]:
-        package = dynamicAnalyst["package"]
-        className = dynamicAnalyst["className"]
-        methodName = dynamicAnalyst["methodName"]
-        analyst = buildDynamicInstance(package, className, configBusiness)
-
-        analysts.append(analyst)
-                   
-    process = DataProcessor()
-
-    for analyst in analysts:
-        process.attach(analyst)
-    process.execute(configBusiness)
-
-    for analyst in analysts:
-        process.detach(analyst)
-
-
-if __name__ == "__main__":
-    configs = loadJson(r"conf/conf.json")
-
-    for config in configs:
-        executeAnalysis(config)
-
-    # 使用多线程时,matplotlib绘图报错
-    # 在使用Python语言的matplotlib包时,如果尝试在多线程环境中创建图形界面,你可能会遇到“QWidget-: Must construct a QApplication before a QWidget”的错误。
-    # 这个错误通常是因为matplotlib的图形后端(backend)依赖于Qt框架,而Qt框架要求在一个QApplication实例被创建之后再创建任何QWidget对象。
-    # 在多线程环境中,每个线程都应该有一个它自己的事件循环。然而,QApplication实例通常是全局的,并且应该只在主线程中创建一次。
-    # 如果你尝试在一个非主线程中创建QApplication或者QWidget,就会遇到这个问题。
-    
-    # with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
-    #     futures=[executor.submit(executeAnalysis, config) for config in configs]        
-
-    #     for future in concurrent.futures.as_completed(futures):
-    #         try:
-    #             result = future.result()
-    #         except Exception as exc:
-    #             print(f'生成异常: {exc}')
-    #         else:
-    #             print(f'任务返回结果: {result}')

+ 0 - 41
wtoaamapi/testListen.py

@@ -1,41 +0,0 @@
-import socket  
-  
-def start_server(port):  
-    # 创建一个socket对象  
-    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
-      
-    # 绑定到指定的IP地址和端口  
-    # 如果你想监听所有可用的网络接口,可以使用空字符串('')作为IP地址  
-    server_address = ('', port)  
-    print(f'Starting up on {server_address[1]}')  
-    server_socket.bind(server_address)  
-      
-    # 开始监听连接  
-    server_socket.listen(1)  
-      
-    while True:  
-        # 等待客户端连接  
-        print('Waiting for a connection')  
-        connection, client_address = server_socket.accept()  
-          
-        try:  
-            print(f'Connection from {client_address}')  
-              
-            # 接收数据,最多1024字节  
-            while True:  
-                data = connection.recv(1024)  
-                print(f'Received {len(data)} bytes from {client_address}')  
-                  
-                if not data:  
-                    break  
-                  
-                # 向客户端发送数据  
-                connection.sendall(data)  
-                  
-        finally:  
-            # 清理连接  
-            connection.close()  
-  
-if __name__ == '__main__':  
-    port = 8123  # 你可以更改为你想要的端口号  
-    start_server(port)