Browse Source

first commit

chenhongyan1989 1 year ago
parent
commit
8bcef7173e
100 changed files with 2243 additions and 6879 deletions
  1. 40 0
      .gitignore
  2. 6 48
      README.md
  3. 0 40
      appService/README.MD
  4. 0 4
      appService/__init__.py
  5. 0 60
      appService/app.py
  6. 0 37
      appService/app.spec
  7. 0 20
      appService/logUtil.py
  8. 0 28
      appService/service/daemonService.py
  9. 0 58
      appService/service/winService.py
  10. 0 100
      conf_template/conf_powerfarm_template.json
  11. 0 152
      conf_template/conf_template.json
  12. 0 4
      dataAnalysisBehavior/__init__.py
  13. 0 21
      dataAnalysisBehavior/behavior/analystExcludeRatedPower.py
  14. 0 181
      dataAnalysisBehavior/behavior/baseAnalyst.py
  15. 0 166
      dataAnalysisBehavior/common/commonBusiness.py
  16. 0 9
      dataAnalysisBehavior/setup.py
  17. 0 4
      dataAnalysisBusiness/__init__.py
  18. 0 1
      dataAnalysisBusiness/algorithm/__init__.py
  19. 0 86
      dataAnalysisBusiness/algorithm/cabinVibrateAnalyst.py
  20. 0 247
      dataAnalysisBusiness/algorithm/cpAnalyst.py
  21. 0 109
      dataAnalysisBusiness/algorithm/cpTrendAnalyst.py
  22. 0 411
      dataAnalysisBusiness/algorithm/dataMarker.py
  23. 0 369
      dataAnalysisBusiness/algorithm/dataProcessor.py
  24. 0 327
      dataAnalysisBusiness/algorithm/generatorSpeedPowerAnalyst.py
  25. 0 236
      dataAnalysisBusiness/algorithm/generatorSpeedTorqueAnalyst.py
  26. 0 120
      dataAnalysisBusiness/algorithm/minPitchAnalyst.py
  27. 0 155
      dataAnalysisBusiness/algorithm/pitchPowerAnalyst.py
  28. 0 98
      dataAnalysisBusiness/algorithm/pitchPowerWindSpeedAnalyst.py
  29. 0 91
      dataAnalysisBusiness/algorithm/pitchTSRCpAnalyst.py
  30. 0 106
      dataAnalysisBusiness/algorithm/powerScatter2DAnalyst.py
  31. 0 119
      dataAnalysisBusiness/algorithm/powerScatterAnalyst.py
  32. 0 375
      dataAnalysisBusiness/algorithm/temperatureLargeComponentsAnalyst.py
  33. 0 102
      dataAnalysisBusiness/algorithm/tsrTrendAnalyst.py
  34. 0 101
      dataAnalysisBusiness/algorithm/windDirectionFrequencyAnalyst.py
  35. 0 44
      dataAnalysisBusiness/algorithm/windSpeedAnalyst.py
  36. 0 83
      dataAnalysisBusiness/algorithm/windSpeedFrequencyAnalyst.py
  37. 0 181
      dataAnalysisBusiness/algorithm/yawErrorAnalyst.py
  38. 0 0
      dataAnalysisBusiness/common/__init__.py
  39. 0 0
      dataAnalysisBusiness/dataContract/__init__.py
  40. 148 0
      dataAnalysisBusiness/dataContract/confBusiness.py
  41. 0 450
      dataAnalysisBusiness/demo/SCADA_10min_category_0.py
  42. 0 507
      dataAnalysisBusiness/demo/SCADA_10min_category_1.py
  43. 0 193
      dataAnalysisBusiness/demo/SCADA_10min_category_2.py
  44. 0 632
      dataAnalysisBusiness/demo/SCADA_10min_category_3.py
  45. 0 62
      dataAnalysisBusiness/demo/scatter3D_plotly.py
  46. 0 50
      dataAnalysisBusiness/demo/scatter3D_plotly_make_subplots.py
  47. 0 19
      dataAnalysisBusiness/demo/test.py
  48. 0 113
      dataAnalysisBusiness/demo/testDataProcess.py
  49. 0 12
      dataAnalysisBusiness/demo/testPandas.py
  50. 1 2
      dataAnalysisBusiness/setup.py
  51. 0 4
      dataContract/__init__.py
  52. 0 88
      dataContract/algorithmContract/dataContract.json
  53. 0 9
      dataContract/setup.py
  54. 0 0
      repository/__init__.py
  55. 1 1
      repository/setup.py
  56. 0 0
      repository/utils/__init__.py
  57. 0 0
      repository/utils/csvFileUtil.py
  58. 0 0
      repository/utils/directoryUtil.py
  59. 0 0
      repository/utils/jsonUtil.py
  60. 0 4
      repositoryZN/__init__.py
  61. 0 0
      wtoaamapi/apps/business/algorithm/__init__.py
  62. 2 2
      wtoaamapi/apps/business/algorithm/analyst.py
  63. 11 0
      wtoaamapi/apps/business/algorithm/analystWithNoCustomeFilter.py
  64. 49 0
      wtoaamapi/apps/business/algorithm/baseAnalyst.py
  65. 13 0
      wtoaamapi/apps/business/algorithm/commonBusiness.py
  66. 197 0
      wtoaamapi/apps/business/algorithm/cpAnalyst.py
  67. 116 0
      wtoaamapi/apps/business/algorithm/cpTrendAnalyst.py
  68. 22 41
      wtoaamapi/apps/business/algorithm/cpWindSpeedAnalyst.py
  69. 2 2
      wtoaamapi/apps/business/algorithm/dataIntegrityOfMinuteAnalyst.py
  70. 15 18
      wtoaamapi/apps/business/algorithm/dataIntegrityOfSecondAnalyst.py
  71. 444 0
      wtoaamapi/apps/business/algorithm/dataProcessor.py
  72. 4 4
      wtoaamapi/apps/business/algorithm/formula_cp.py
  73. 92 0
      wtoaamapi/apps/business/algorithm/generatorSpeedPowerAnalyst.py
  74. 62 0
      wtoaamapi/apps/business/algorithm/generatorSpeedTorqueAnalyst.py
  75. 122 0
      wtoaamapi/apps/business/algorithm/minPitchAnalyst.py
  76. 15 19
      wtoaamapi/apps/business/algorithm/pitchGeneratorSpeedAnalyst.py
  77. 59 0
      wtoaamapi/apps/business/algorithm/pitchPowerAnalyst.py
  78. 29 53
      wtoaamapi/apps/business/algorithm/powerCurveAnalyst.py
  79. 6 6
      wtoaamapi/apps/business/algorithm/powerOscillationAnalyst.py
  80. 13 24
      wtoaamapi/apps/business/algorithm/ratedPowerWindSpeedAnalyst.py
  81. 4 4
      wtoaamapi/apps/business/algorithm/ratedWindSpeedAnalyst.py
  82. 11 16
      wtoaamapi/apps/business/algorithm/temperatureEnvironmentAnalyst.py
  83. 143 0
      wtoaamapi/apps/business/algorithm/temperatureLargeComponentsAnalyst.py
  84. 17 42
      wtoaamapi/apps/business/algorithm/tsrAnalyst.py
  85. 125 0
      wtoaamapi/apps/business/algorithm/tsrTrendAnalyst.py
  86. 15 37
      wtoaamapi/apps/business/algorithm/tsrWindSpeedAnalyst.py
  87. 0 0
      wtoaamapi/apps/business/algorithm/utils/__init__.py
  88. 57 0
      wtoaamapi/apps/business/algorithm/utils/csvFileUtil.py
  89. 89 0
      wtoaamapi/apps/business/algorithm/utils/directoryUtil.py
  90. 0 0
      wtoaamapi/apps/business/algorithm/utils/jsonUtil/__init__.py
  91. 37 0
      wtoaamapi/apps/business/algorithm/utils/jsonUtil/jsonUtil.py
  92. 21 0
      wtoaamapi/apps/business/algorithm/utils/test.py
  93. 6 11
      wtoaamapi/apps/business/algorithm/windRoseOfTurbine.py
  94. 176 0
      wtoaamapi/apps/business/algorithm/yawErrorAnalyst.py
  95. 24 59
      wtoaamapi/apps/business/confBusiness.py
  96. 22 10
      wtoaamapi/apps/business/main.py
  97. 25 50
      wtoaamapi/apps/business/test.py
  98. 1 1
      wtoaamapi/apps/business/turbineInfo.py
  99. 1 0
      wtoaamapi/apps/viewDemo/viewUser.py
  100. 0 41
      wtoaamapi/testListen.py

+ 40 - 0
.gitignore

@@ -0,0 +1,40 @@
+# Autosave files
+*.asv
+*.m~
+*.autosave
+*.slx.r*
+*.mdl.r*
+
+# Derived content-obscured files
+*.p
+
+# Compiled MEX files
+*.mex*
+
+# Packaged app and toolbox files
+*.mlappinstall
+*.mltbx
+
+# Deployable archives
+*.ctf
+
+# Generated helpsearch folders
+helpsearch*/
+
+# Code generation folders
+slprj/
+sccprj/
+codegen/
+
+# Cache files
+*.slxc
+
+# Cloud based storage dotfile
+.MATLABDriveTag
+
+**/conf/
+**/target/
+**/output/
+**/testData/
+**/testOutput/
+**/source_code_original/

+ 6 - 48
README.md

@@ -3,51 +3,9 @@ WTOAAM
 
 风力发电机组运行分析算法模型 Wind turbine operation analysis algorithm model
 
-# django auth
-    http://127.0.0.1/admin
-    账号
-    admin
-    密码
-    root.123456
-
-
-# 附加进程调试
-## 安装debugpy
-    pip install --upgrade debugpy
-
-## 运行程序命令
-   示例:python -m debugpy --listen localhost:5678 --wait-for-client e:/WorkSpace/SourceCode/WTOAAM/wtoaamapi/apps/business/main.py
-
-## 附加进程
-   配置launch.json文件,内容如下:
-   {
-    // Use IntelliSense to learn about possible attributes.
-    // Hover to view descriptions of existing attributes.
-    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
-    "version": "0.2.0",
-    "configurations": [
-        {
-            "name": "Python: Remote Attach",  // 远程调试
-            "type":"debugpy",
-            "request": "attach",
-            "connect": {
-                "host": "localhost",
-                "port": 5678
-            },
-            "pathMappings": [
-                {
-                    "localRoot": "${workspaceFolder}",
-                    "remoteRoot": "."
-                }
-            ],
-            "justMyCode": false
-        }
-        ]
-    }
-    打开要调试的源代码文件,在vscode搜索框选择"开始调试 debug","Python: Remote Attach"即lanuch.json文件中configurations[0].name节点值即可。
-
-# 自宿主服务
-  自定义包appService,同时支持daemon、windows服务
-
-## 依赖包
-    pip install python-daemon pypiwin32
+django auth
+http://127.0.0.1/admin
+账号
+admin
+密码
+root.123456

+ 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 - 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 - 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 - 4
dataAnalysisBehavior/__init__.py

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

+ 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

+ 0 - 181
dataAnalysisBehavior/behavior/baseAnalyst.py

@@ -1,181 +0,0 @@
-from abc import ABC, abstractmethod
-import pandas as pd
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-from common.commonBusiness import CommonBusiness
-
-
-class BaseAnalyst(ABC):
-    def __init__(self, confData: ConfBusiness):
-        self.common = CommonBusiness()
-        self.confData = confData
-        self.node_filter_value_state_turbine = "filter_value_state_turbine"
-        self.node_angle_pitch_min = "angle_pitch_min"
-        self.node_angle_pitch_max = "angle_pitch_max"
-        self.node_speed_wind_cut_in = "speed_wind_cut_in"
-        self.node_speed_wind_cut_out = "speed_wind_cut_out"
-        self.node_active_power_min = "active_power_min"
-        self.node_active_power_max = "active_power_max"
-        self.node_speed_generator_min = "speed_generator_min"
-        self.node_speed_generator_max = "speed_generator_max"
-        self.node_activePowerAvailable = "activePowerAvailable"
-        self.dataFrameContractOfTurbine = self.processContractData(confData)
-
-    def processContractData(self, confData: ConfBusiness):
-        dataFrameContractOfTurbine = self.common.contractGuaranteePowerCurveData(
-            confData.turbineGuaranteedPowerCurveFilePathCSV, confData)
-        self.common.calculateCp2(
-            dataFrameContractOfTurbine, confData.density_air, confData.rotor_diameter, "风速", "有功功率")
-
-        return dataFrameContractOfTurbine
-
-    @abstractmethod
-    def typeAnalyst(self):
-        pass
-
-    def getOutputAnalysisDir(self):
-        """
-        获取当前分析的输出目录
-        """
-        outputAnalysisDir = r"{}/{}".format(
-            self.confData.output_path, self.typeAnalyst())
-        dir.create_directory(outputAnalysisDir)
-
-        return outputAnalysisDir
-
-    def filterCommon(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        if not self.common.isNone(confData.field_wind_speed) and self.node_speed_wind_cut_in in confData.filter \
-                and not self.common.isNone(confData.filter[self.node_speed_wind_cut_in]) \
-                and self.node_speed_wind_cut_out in confData.filter \
-                and not self.common.isNone(confData.filter[self.node_speed_wind_cut_out]) \
-                and not self.common.isNone(confData.field_power):
-            windSpeedCutIn = float(
-                confData.filter[self.node_speed_wind_cut_in])
-            windSpeedCutOut = float(
-                confData.filter[self.node_speed_wind_cut_out])
-
-            dataFrame = dataFrame[~((dataFrame[confData.field_wind_speed] > windSpeedCutOut) | (
-                dataFrame[confData.field_wind_speed] < windSpeedCutIn))]
-            dataFrame = dataFrame[~((dataFrame[confData.field_wind_speed] > windSpeedCutIn) & (
-                dataFrame[confData.field_power] < confData.rated_power*0.01))]
-
-        if not self.common.isNone(confData.field_power) and confData.field_power in dataFrame.columns \
-           and not self.common.isNone(confData.field_pitch_angle1) and confData.field_pitch_angle1 in dataFrame.columns \
-           and self.node_angle_pitch_min in confData.filter and not self.common.isNone(confData.filter[self.node_angle_pitch_min]):
-            anglePitchMin = float(confData.filter[self.node_angle_pitch_min])
-            dataFrame = dataFrame[~((
-                dataFrame[confData.field_power] <= confData.rated_power*0.9) & (dataFrame[confData.field_pitch_angle1] >anglePitchMin) )]
-        
-        # if not self.common.isNone(confData.rated_WindSpeed) 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]):
-        #     anglePitchMin = float(confData.filter[self.node_angle_pitch_min])
-
-        #     dataFrame = dataFrame[~((dataFrame[confData.field_wind_speed] < confData.rated_WindSpeed) & (
-        #         dataFrame[confData.field_pitch_angle1] > anglePitchMin))]
-            
-        # Filter rows where turbine state
-        if not self.common.isNone(confData.field_gen_speed) and confData.field_gen_speed in dataFrame.columns:
-            dataFrame = dataFrame[(dataFrame[confData.field_gen_speed] > 0)]
-
-        # Filter rows where turbine state
-        if not self.common.isNone(confData.field_turbine_state) and self.node_filter_value_state_turbine in confData.filter and not self.common.isNone(confData.filter[self.node_filter_value_state_turbine]):
-            stateTurbine = confData.filter[self.node_filter_value_state_turbine]
-            dataFrame = dataFrame[dataFrame[confData.field_turbine_state].isin(
-                stateTurbine)]
-
-        if not self.common.isNone(confData.rated_WindSpeed) and not self.common.isNone(confData.field_wind_speed) \
-                and not self.common.isNone(confData.field_gen_speed) \
-                and self.node_speed_generator_max in confData.filter \
-                and not self.common.isNone(confData.filter[self.node_speed_generator_max]):
-            speedGeneratorMax = float(
-                confData.filter[self.node_speed_generator_max])
-
-            dataFrame = dataFrame[~((dataFrame[confData.field_wind_speed] >= confData.rated_WindSpeed) & (
-                dataFrame[confData.field_gen_speed] < speedGeneratorMax*0.9))]
-
-        if not self.common.isNone(confData.field_gen_speed) \
-            and self.node_speed_generator_max in confData.filter \
-                and not self.common.isNone(confData.filter[self.node_speed_generator_min]) \
-                and not self.common.isNone(confData.filter[self.node_speed_generator_max]):
-            speedGeneratorMin = float(
-                confData.filter[self.node_speed_generator_min])
-            speedGeneratorMax = float(
-                confData.filter[self.node_speed_generator_max])
-            dataFrame = dataFrame[~((dataFrame[confData.field_gen_speed] < speedGeneratorMin) | (
-                dataFrame[confData.field_gen_speed] > speedGeneratorMax))]
-
-        if not self.common.isNone(confData.field_activePowerSet) and confData.field_activePowerSet in dataFrame.columns:
-            dataFrame = dataFrame[dataFrame[confData.field_activePowerSet]
-                                  == confData.rated_power]
-
-        if not self.common.isNone(confData.field_activePowerAvailable) and confData.field_activePowerAvailable in dataFrame.columns \
-              and self.node_activePowerAvailable in confData.filter and not self.common.isNone(confData.filter[self.node_activePowerAvailable]):
-            state = confData.filter[self.node_activePowerAvailable]
-            dataFrame = dataFrame[dataFrame[confData.field_activePowerAvailable].isin(
-                state)]
-
-        # # Filter rows where pitch
-        # if 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]):
-        #     anglePitchMin = float(confData.filter[self.node_angle_pitch_min])
-        #     dataFrame = dataFrame[(
-        #         dataFrame[confData.field_pitch_angle1] < anglePitchMin)]
-
-        # if not self.common.isNone(confData.field_pitch_angle1) and self.node_angle_pitch_max in confData.filter and not self.common.isNone(confData.filter[self.node_angle_pitch_max]):
-        #     anglePitchMax = float(confData.filter[self.node_angle_pitch_max])
-        #     dataFrame = dataFrame[(
-        #         dataFrame[confData.field_pitch_angle1] <= anglePitchMax)]
-
-        # # Filter rows where wind speed
-        # if not self.common.isNone(confData.field_wind_speed) and self.node_speed_wind_cut_in in confData.filter and not self.common.isNone(confData.filter[self.node_speed_wind_cut_in]):
-        #     windSpeedCutIn = float(confData.filter[self.node_speed_wind_cut_in])
-        #     dataFrame = dataFrame[(
-        #         dataFrame[confData.field_wind_speed] >= windSpeedCutIn)]
-
-        # if not self.common.isNone(confData.field_wind_speed) and self.node_speed_wind_cut_out in confData.filter and not self.common.isNone(confData.filter[self.node_speed_wind_cut_out]):
-        #     windSpeedCutOut = float(confData.filter[self.node_speed_wind_cut_out])
-        #     dataFrame = dataFrame[(
-        #         dataFrame[confData.field_wind_speed] < windSpeedCutOut)]
-
-        # # Filter rows where power
-        # if not self.common.isNone(confData.field_power) and self.node_active_power_min in confData.filter and not self.common.isNone(confData.filter[self.node_active_power_min]):
-        #     activePowerMin = float(confData.filter[self.node_active_power_min])
-        #     dataFrame = dataFrame[(
-        #         dataFrame[confData.field_power] >= activePowerMin)]
-
-        # 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]):
-        #     activePowerMax = float(confData.filter[self.node_active_power_max])
-        #     dataFrame = dataFrame[(
-        #         dataFrame[confData.field_power] < activePowerMax)]
-
-        return dataFrame
-
-    def filterCustomForTurbine(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        return self.filterCommon(dataFrame, confData)
-
-    def analysisOfTurbine(self,
-                          dataFrame: pd.DataFrame,
-                          outputAnalysisDir,
-                          outputFilePath,
-                          confData: ConfBusiness,
-                          turbineName):
-        dataFrame = self.filterCustomForTurbine(dataFrame, confData)
-        self.turbineAnalysis(dataFrame, outputAnalysisDir,
-                             outputFilePath, confData, turbineName)
-
-    def turbineAnalysis(self,
-                        dataFrame: pd.DataFrame,
-                        outputAnalysisDir,
-                        outputFilePath,
-                        confData: ConfBusiness,
-                        turbineName):
-        pass
-
-    def filterCustomForTurbines(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        return self.filterCommon(dataFrame, confData)
-
-    def analysisOfTurbines(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        dataFrameMerge = self.filterCustomForTurbines(dataFrameMerge, confData)
-        self.turbinesAnalysis(dataFrameMerge, outputAnalysisDir, confData)
-
-    def turbinesAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        pass

+ 0 - 166
dataAnalysisBehavior/common/commonBusiness.py

@@ -1,166 +0,0 @@
-import pandas as pd
-import numpy as np
-import chardet  
-from algorithmContract.confBusiness import *
-
-
-class CommonBusiness:
-    def getFloat32(self):
-        return "float32"
-
-    def isNone(self, value):
-        return value is None
-
-    def getUseColumns(self, confData: ConfBusiness):
-        useColumns = []
-
-        if not self.isNone(confData.field_turbine_time):
-            useColumns.append(confData.field_turbine_time)
-
-        if not self.isNone(confData.field_turbine_name):
-            useColumns.append(confData.field_turbine_name)
-
-        if not self.isNone(confData.field_wind_speed):
-            useColumns.append(confData.field_wind_speed)
-
-        if not self.isNone(confData.field_power):
-            useColumns.append(confData.field_power)
-
-        if not self.isNone(confData.field_pitch_angle1):
-            useColumns.append(confData.field_pitch_angle1)
-
-        if not self.isNone(confData.field_rotor_speed):
-            useColumns.append(confData.field_rotor_speed)
-
-        if not self.isNone(confData.field_gen_speed):
-            useColumns.append(confData.field_gen_speed)
-
-        if not self.isNone(confData.field_torque):
-            useColumns.append(confData.field_torque)
-
-        if not self.isNone(confData.field_wind_dir):
-            useColumns.append(confData.field_wind_dir)
-
-        if not self.isNone(confData.field_angle_included):
-            useColumns.append(confData.field_angle_included)
-
-        if not self.isNone(confData.field_nacelle_pos):
-            useColumns.append(confData.field_nacelle_pos)
-
-        if not self.isNone(confData.field_env_temp):
-            useColumns.append(confData.field_env_temp)
-
-        if not self.isNone(confData.field_nacelle_temp):
-            useColumns.append(confData.field_nacelle_temp)
-
-        if not self.isNone(confData.field_turbine_state):
-            useColumns.append(confData.field_turbine_state)
-
-        if not self.isNone(confData.field_Cabin_Vibrate_X):
-            useColumns.append(confData.field_Cabin_Vibrate_X)
-
-        if not self.isNone(confData.field_Cabin_Vibrate_Y):
-            useColumns.append(confData.field_Cabin_Vibrate_Y)
-
-        if not self.isNone(confData.field_activePowerSet):
-            useColumns.append(confData.field_activePowerSet)
-
-        if not self.isNone(confData.field_activePowerAvailable):
-            useColumns.append(confData.field_activePowerAvailable)
-
-        if not self.isNone(confData.field_temperature_large_components):
-            temperature_cols = confData.field_temperature_large_components.split(
-                ',')
-            for temperatureColumn in temperature_cols:
-                useColumns.append(temperatureColumn)
-
-        return useColumns
-
-    def recalculationOfGeneratorSpeedforShow(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        if not self.isNone(confData.value_gen_speed_multiple) and confData.field_gen_speed in dataFrame.columns:
-            dataFrame[confData.field_gen_speed] = dataFrame[confData.field_gen_speed] * \
-                confData.value_gen_speed_multiple
-            dataFrame[Field_GeneratorTorque] = dataFrame[confData.field_gen_speed]
-
-    def contractGuaranteePowerCurveData(self, csvPowerCurveFilePath, confData: ConfBusiness):
-        with open(csvPowerCurveFilePath, 'rb') as f:  
-            result = chardet.detect(f.read())  
-        
-        dataFrameGuaranteePowerCurve = pd.read_csv(csvPowerCurveFilePath, encoding= result['encoding'] )
-
-        return dataFrameGuaranteePowerCurve
-
-    def calculateTSR(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        """
-        使用叶轮/主轴转速、风速、叶轮直径,计算叶尖速比(TSR)
-        """
-        # Calculate tsr
-        if not self.isNone(confData.rotor_diameter) \
-           and not self.isNone(confData.field_wind_speed) and confData.field_wind_speed in dataFrame.columns \
-           and not self.isNone(confData.field_rotor_speed) and confData.field_rotor_speed in dataFrame.columns:
-            dataFrame[confData.field_rotor_speed] = pd.to_numeric(
-                dataFrame[confData.field_rotor_speed], errors='coerce')
-            dataFrame[confData.field_wind_speed] = pd.to_numeric(
-                dataFrame[confData.field_wind_speed], errors='coerce')
-
-            rotor_diameter = pd.to_numeric(
-                confData.rotor_diameter, errors='coerce')
-            
-            dataFrame[Field_PowerFloor] = dataFrame[confData.field_power].apply(  
-                lambda x: int(x / 10) * 10 if pd.notnull(x) else np.nan  # 保留NaN值  
-            )
-
-            dataFrame[Field_TSR] = (dataFrame[confData.field_rotor_speed] * 0.104667 *
-                                    (rotor_diameter / 2)) / dataFrame[confData.field_wind_speed]
-            
-    def calculateCp2(self,dataFrame: pd.DataFrame,airDensity,rotorDiameter,fieldWindSpeed,fieldActivePower):
-        """
-        使用有功功率、风速、空气密度、叶轮直径,计算风能利用系数(Cp)
-        """
-        # Calculate cp
-        if not self.isNone(airDensity) and not self.isNone(rotorDiameter) \
-           and not self.isNone(fieldWindSpeed) and fieldWindSpeed in dataFrame.columns \
-           and not self.isNone(fieldActivePower) and fieldActivePower in dataFrame.columns:
-            dataFrame[fieldActivePower] = pd.to_numeric(
-                dataFrame[fieldActivePower], errors='coerce')
-            dataFrame[fieldWindSpeed] = pd.to_numeric(
-                dataFrame[fieldWindSpeed], errors='coerce')
-
-            rotor_diameter = pd.to_numeric(
-                rotorDiameter, errors='coerce')
-            air_density = pd.to_numeric(airDensity, errors='coerce')
-
-            dataFrame[Field_PowerFloor] = dataFrame[fieldActivePower].apply(  
-                lambda x: int(x / 10) * 10 if pd.notnull(x) else np.nan  # 保留NaN值  
-            )
-
-            dataFrame[Field_Cp] = dataFrame[fieldActivePower] * 1000 / \
-                (0.5 * np.pi * air_density *
-                 (rotor_diameter ** 2) / 4 * dataFrame[fieldWindSpeed] ** 3)
-
-    def calculateCp(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        """
-        使用有功功率、风速、空气密度、叶轮直径,计算风能利用系数(Cp)
-        """
-        self.calculateCp2(dataFrame,confData.density_air,confData.rotor_diameter,confData.field_wind_speed,confData.field_power)
-        # # Calculate cp
-        # if not self.isNone(confData.density_air) and not self.isNone(confData.rotor_diameter) \
-        #    and not self.isNone(confData.field_wind_speed) and confData.field_wind_speed in dataFrame.columns \
-        #    and not self.isNone(confData.field_power) and confData.field_power in dataFrame.columns:
-        #     dataFrame[confData.field_power] = pd.to_numeric(
-        #         dataFrame[confData.field_power], errors='coerce')
-        #     dataFrame[confData.field_wind_speed] = pd.to_numeric(
-        #         dataFrame[confData.field_wind_speed], errors='coerce')
-
-        #     rotor_diameter = pd.to_numeric(
-        #         confData.rotor_diameter, errors='coerce')
-        #     air_density = pd.to_numeric(confData.density_air, errors='coerce')
-
-        #     dataFrame[Field_PowerFloor] = dataFrame[confData.field_power].apply(  
-        #         lambda x: int(x / 10) * 10 if pd.notnull(x) else np.nan  # 保留NaN值  
-        #     )
-
-        #     dataFrame[Field_Cp] = dataFrame[confData.field_power] * 1000 / \
-        #         (0.5 * np.pi * air_density *
-        #          (rotor_diameter ** 2) / 4 * dataFrame[confData.field_wind_speed] ** 3)
-            

+ 0 - 9
dataAnalysisBehavior/setup.py

@@ -1,9 +0,0 @@
-from setuptools import setup, find_packages
-
-setup(
-    name='dataAnalysisBehavior',
-    version='1.0.202403261044',
-    description='Data Analysis Behavior Package', # 描述信息
-    author='Xie Zhou Yang', # 作者
-    packages=find_packages()
-)

+ 0 - 4
dataAnalysisBusiness/__init__.py

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

+ 0 - 1
dataAnalysisBusiness/algorithm/__init__.py

@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-

+ 0 - 86
dataAnalysisBusiness/algorithm/cabinVibrateAnalyst.py

@@ -1,86 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import matplotlib.pyplot as plt
-import plotly.graph_objects as go
-from plotly.subplots import make_subplots
-import plotly.express as px  
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-from windrose import WindroseAxes
-import matplotlib as mpl
-
-class CabinVibrateAnalyst(Analyst):
-    '''
-    风电机组机舱振动分析
-    '''
-    def typeAnalyst(self):
-        return "cabin_vibrate"
-    
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.CabinVibrateAnalysis(dataFrameMerge, outputAnalysisDir, confData)
-    
-    def CabinVibrateAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # 检查所需列是否存在
-        required_columns = {confData.field_wind_dir, confData.field_wind_speed, confData.field_Cabin_Vibrate_X, confData.field_Cabin_Vibrate_Y}
-        # Cabin_Vibrate_X为左右振动 Cabin_Vibrate_Y为前后振动
-
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-        
-        dataFrameMerge = dataFrameMerge.dropna(axis=0, how='any')
-        
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        for name, group in grouped:
-            # 风向- 左右振动 - 风速
-            fig = plt.figure(figsize=(6,6))
-            pointsize = 6 # 散点的大小
-            ax = WindroseAxes.from_ax()
-            ax.set_theta_zero_location("N")
-            ax.set_theta_direction('clockwise')
-            ax.set_xticks([(i/8)*np.pi for i in range(16)])
-            ax.set_xticklabels(['N', 'NNE', 'NE', 'ENE', 'E','ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'])
-            #ax.set_rlim(0, 0.6, 0.1, auto=False)
-            # 设定极轴的范围,即设定图片显示振动大小的范围
-            ax.set_rlabel_position(34)
-            # 将显示极轴刻度的位置设在34°方向上,即位于NNE与NE的中间
-            plt.scatter(np.radians(group[confData.field_wind_dir]), group[confData.field_Cabin_Vibrate_Y],
-                        c=group[confData.field_wind_speed], cmap='viridis_r', alpha=1, s=pointsize)
-            plt.title('WindDirecton-Cabin_VibrationX-Windspeed')
-            locator = mpl.ticker.MultipleLocator(1)
-            # 设置颜色条上的风速间隔为1
-            cb = plt.colorbar(ticks=locator, pad=0.05, shrink=0.65) 
-            # 颜色条与图像保持一定距离,防止重叠现象
-            cb.ax.tick_params()
-            cb.ax.set_title('Wind Speedd(m/s)')
-            # 保存图像
-            plt.savefig(os.path.join(outputAnalysisDir, "{}Cabin_Vibrate_X.png".format(name)), bbox_inches='tight', dpi=120)
-            
-            # 风向- 前后振动 - 风速
-            fig = plt.figure(figsize=(6,6))
-            pointsize = 6 # 散点的大小
-            ax = WindroseAxes.from_ax()
-            ax.set_theta_zero_location("N")
-            ax.set_theta_direction('clockwise')
-            ax.set_xticks([(i/8)*np.pi for i in range(16)])
-            ax.set_xticklabels(['N', 'NNE', 'NE', 'ENE', 'E','ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'])
-            #ax.set_rlim(0, 0.6, 0.1, auto=False)
-            # 设定极轴的范围,即设定图片显示振动大小的范围
-            ax.set_rlabel_position(34)
-            # 将显示极轴刻度的位置设在34°方向上,即位于NNE与NE的中间
-            plt.scatter(np.radians(group[confData.field_wind_dir]), group[confData.field_Cabin_Vibrate_Y],
-                        c=group[confData.field_wind_speed], cmap='viridis_r', alpha=1, s=pointsize)
-            plt.title('WindDirecton-Cabin_VibrationX-Windspeed')
-            locator = mpl.ticker.MultipleLocator(1)
-            # 设置颜色条上的风速间隔为1
-            cb = plt.colorbar(ticks=locator, pad=0.05, shrink=0.65) 
-            # 颜色条与图像保持一定距离,防止重叠现象
-            cb.ax.tick_params()
-            cb.ax.set_title('Wind Speedd(m/s)')
-            # 保存图像
-            plt.savefig(os.path.join(outputAnalysisDir, "{}Cabin_Vibrate_Y.png".format(name)), bbox_inches='tight', dpi=120) 
-
-

+ 0 - 247
dataAnalysisBusiness/algorithm/cpAnalyst.py

@@ -1,247 +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 behavior.analystExcludeRatedPower import AnalystExcludeRatedPower
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-
-class CpAnalyst(AnalystExcludeRatedPower):
-    """
-    风电机组风能利用系数分析
-    """
-
-    def typeAnalyst(self):
-        return "cp"        
-
-    def turbinesAnalysis(self, dataFrameMerge:pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):        
-        # 检查所需列是否存在
-        required_columns = {confData.field_wind_speed,
-                            Field_Cp, Field_PowerFloor}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-        
-        self.drawLineGraphForTurbine(dataFrameMerge,outputAnalysisDir,confData)
-
-    def drawLineGraphForTurbine(self,dataFrameMerge:pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        sns.set_palette('deep')
-
-        upLimitOfPower = confData.rated_power*0.9
-        value_step = 0.5
-        
-        grouped = dataFrameMerge.groupby([Field_NameOfTurbine,Field_PowerFloor]).agg(
-            cp=('cp', 'median'),
-            cp_max=('cp', 'max'),
-            cp_min=('cp', 'min'),
-        ).reset_index()
-
-        # Rename columns post aggregation for clarity
-        grouped.columns = [Field_NameOfTurbine,Field_PowerFloor,  Field_CpMedian, 'cp_max', 'cp_min']
-        # Sort by power_floor
-        grouped = grouped.sort_values(by=[Field_NameOfTurbine,Field_PowerFloor])
-
-        fig, ax = plt.subplots()
-
-        ax = sns.lineplot(x=Field_PowerFloor, y=Field_CpMedian, data=grouped,
-                          hue=Field_NameOfTurbine)
-        # 绘制合同功率曲线  
-        ax.plot(self.dataFrameContractOfTurbine[Field_PowerFloor], self.dataFrameContractOfTurbine[Field_Cp], marker='o',  
-                c='red', label='Contract Guarantee Cp')  
-
-        ax.xaxis.set_major_locator(MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                        confData.graphSets["activePower"]) and not self.common.isNone(
-                        confData.graphSets["activePower"]["step"]) else 250))  # 创建一个刻度 ,将定位器应用到y轴上
-        ax.set_xlim(0, upLimitOfPower)
-
-        ax.yaxis.set_major_locator(MultipleLocator(
-            confData.graphSets["cp"]["step"] if not self.common.isNone(confData.graphSets["cp"]["step"]) else value_step))  # 创建一个刻度 ,将定位器应用到y轴上
-        ax.set_ylim(confData.graphSets["cp"]["min"] if not self.common.isNone(confData.graphSets["cp"]["min"])
-                    else 0, confData.graphSets["cp"]["max"] if not self.common.isNone(confData.graphSets["cp"]["max"]) else 2)
-
-        ax.set_title('Cp-Distribution')
-        # plt.legend(ncol=4)
-        plt.xticks(rotation=45)  # 旋转45度
-        plt.legend(title='Turbine', bbox_to_anchor=(1.02, 0.5),
-                   ncol=2, loc='center left', borderaxespad=0.)
-        plt.savefig(os.path.join(
-            outputAnalysisDir, "{}-Cp-Distribution.png".format(confData.farm_name)), bbox_inches='tight', dpi=300)
-        plt.close()
-
-        groupedX=grouped.groupby(Field_NameOfTurbine)
-
-        for name,group in groupedX:
-            color = ["lightgrey"] * len(dataFrameMerge[Field_NameOfTurbine].unique())
-            fig, ax = plt.subplots(figsize=(8, 8))
-            ax = sns.lineplot(x=Field_PowerFloor, y=Field_CpMedian, data=grouped, hue=Field_NameOfTurbine,
-                              palette=sns.color_palette(color), legend=False)
-            ax = sns.lineplot(x=Field_PowerFloor, y=Field_CpMedian, data=group,
-                              color='darkblue', legend=False)
-            
-            # 绘制合同功率曲线  
-            ax.plot(self.dataFrameContractOfTurbine[Field_PowerFloor], self.dataFrameContractOfTurbine[Field_Cp], marker='o',  
-                c='red', label='Contract Guarantee Cp')  
-
-            ax.xaxis.set_major_locator(
-                MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                        confData.graphSets["activePower"]) and not self.common.isNone(
-                        confData.graphSets["activePower"]["step"]) else 250))  # 创建一个刻度 ,将定位器应用到y轴上
-            ax.set_xlim(0, upLimitOfPower)
-
-            ax.yaxis.set_major_locator(MultipleLocator(
-                confData.graphSets["cp"]["step"] if not self.common.isNone(confData.graphSets["cp"]["step"]) else value_step))  # 创建一个刻度 ,将定位器应用到y轴上
-            ax.set_ylim(confData.graphSets["cp"]["min"] if not self.common.isNone(confData.graphSets["cp"]["min"])
-                        else 0, confData.graphSets["cp"]["max"] if not self.common.isNone(confData.graphSets["cp"]["max"]) else 1)
-
-            ax.set_title('turbine name={}'.format(name))
-            plt.xticks(rotation=45)  # 旋转45度
-            plt.legend(title='Turbine', bbox_to_anchor=(1.02, 0.5),
-                   ncol=2, loc='center left', borderaxespad=0.)
-            plt.savefig(os.path.join(outputAnalysisDir, "{}.png".format(
-                name)), bbox_inches='tight', dpi=120)
-            plt.close()
-
-
-    def generate_cp_distribution(self, csvFileDirOfCp, confData: ConfBusiness, encoding='utf-8'):
-        """
-        Generates Cp distribution plots for turbines in a wind farm.
-
-        Parameters:
-        - csvFileDirOfCp: str, path to the directory containing input CSV files.
-        - farm_name: str, name of the wind farm.
-        - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
-        """
-
-        output_path = csvFileDirOfCp
-
-        field_Name_Turbine = "turbine_name"
-        x_name = 'power_floor'
-        y_name = 'cp'
-        upLimitOfPower = confData.rated_power*0.9
-        value_step = 0.5
-
-        sns.set_palette('deep')
-        res = pd.DataFrame()
-        for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
-            for file_name in file_names:
-
-                if not file_name.endswith(CSVSuffix):
-                    continue
-
-                file_path = os.path.join(root, file_name)
-                print(file_path)
-                frame = pd.read_csv(file_path, encoding=encoding)
-                frame = frame[(frame[x_name] > 0)]
-                turbine_name = file_name.split(CSVSuffix)[0]
-                frame[field_Name_Turbine] = turbine_name
-
-                res = pd.concat(
-                    [res, frame.loc[:, [field_Name_Turbine, x_name, y_name]]], axis=0)
-
-        ress = res.reset_index()
-
-        fig, ax = plt.subplots()
-
-        ax = sns.lineplot(x=x_name, y=y_name, data=ress,
-                          hue=field_Name_Turbine)
-
-        ax.xaxis.set_major_locator(MultipleLocator(200))  # 创建一个刻度 ,将定位器应用到y轴上
-        ax.set_xlim(0, upLimitOfPower)
-
-        ax.yaxis.set_major_locator(MultipleLocator(
-            confData.graphSets["cp"]["step"] if not self.common.isNone(confData.graphSets["cp"]["step"]) else value_step))  # 创建一个刻度 ,将定位器应用到y轴上
-        ax.set_ylim(confData.graphSets["cp"]["min"] if not self.common.isNone(confData.graphSets["cp"]["min"])
-                    else 0, confData.graphSets["cp"]["max"] if not self.common.isNone(confData.graphSets["cp"]["max"]) else 2)
-
-        ax.set_title('Cp-Distribution')
-        # plt.legend(ncol=4)
-        plt.xticks(rotation=45)  # 旋转45度
-        plt.legend(title='turbine', bbox_to_anchor=(1.02, 0.5),
-                   ncol=2, loc='center left', borderaxespad=0.)
-        plt.savefig(os.path.join(
-            output_path, "{}-Cp-Distribution.png".format(confData.farm_name)), bbox_inches='tight', dpi=300)
-        plt.close()
-
-        grouped = ress.groupby(field_Name_Turbine)
-        for name, group in grouped:
-            color = ["lightgrey"] * len(ress[field_Name_Turbine].unique())
-            fig, ax = plt.subplots(figsize=(8, 8))
-            ax = sns.lineplot(x=x_name, y=y_name, data=ress, hue=field_Name_Turbine,
-                              palette=sns.color_palette(color), legend=False)
-            ax = sns.lineplot(x=x_name, y=y_name, data=group,
-                              color='darkblue', legend=False)
-
-            ax.xaxis.set_major_locator(
-                MultipleLocator(200))  # 创建一个刻度 ,将定位器应用到y轴上
-            ax.set_xlim(0, upLimitOfPower)
-
-            ax.yaxis.set_major_locator(MultipleLocator(
-                confData.graphSets["cp"]["step"] if not self.common.isNone(confData.graphSets["cp"]["step"]) else value_step))  # 创建一个刻度 ,将定位器应用到y轴上
-            ax.set_ylim(confData.graphSets["cp"]["min"] if not self.common.isNone(confData.graphSets["cp"]["min"])
-                        else 0, confData.graphSets["cp"]["max"] if not self.common.isNone(confData.graphSets["cp"]["max"]) else 1)
-
-            ax.set_title('turbine name={}'.format(name))
-            plt.xticks(rotation=45)  # 旋转45度
-            plt.savefig(os.path.join(output_path, "{}.png".format(
-                name)), bbox_inches='tight', dpi=120)
-            plt.close()
-
-    def plot_cp_distribution(self, csvFileDir,  farm_name):
-        field_Name_Turbine = "设备名"
-        x_name = 'power_floor'
-        y_name = 'cp'
-        # Create the output path based on the farm name
-        output_path = csvFileDir  # output_path_template.format(farm_name)
-        # Ensure the output directory exists
-        os.makedirs(output_path, exist_ok=True)
-        print(csvFileDir)
-        # Initialize a DataFrame to store results
-        res = pd.DataFrame()
-
-        # Walk through the input directory to process each file
-        for root, _, file_names in dir.list_directory(csvFileDir):
-            for file_name in file_names:
-                full_path = os.path.join(root, file_name)
-                frame = pd.read_csv(full_path, encoding='gbk')
-                turbine_name = file_name.split(CSVSuffix)[0]
-                print("turbine_name={}".format(turbine_name))
-                frame[field_Name_Turbine] = turbine_name
-                res = pd.concat(
-                    [res, frame.loc[:, [field_Name_Turbine, x_name, y_name]]], axis=0)
-
-        # Reset index for plotting
-        ress = res.reset_index(drop=True)
-
-        # Plot combined Cp distribution for all turbines
-        fig = make_subplots(rows=1, cols=1)
-        for name, group in ress.groupby(field_Name_Turbine):
-            fig.add_trace(go.Scatter(
-                x=group[x_name], y=group[y_name], mode='lines', name=name))
-
-        fig.update_layout(title_text='{} Cp分布'.format(
-            farm_name), xaxis_title=x_name, yaxis_title=y_name)
-        fig.write_image(os.path.join(
-            output_path, "{}Cp分布.png".format(farm_name)), scale=3)
-
-        # Plot individual Cp distributions
-        unique_turbines = ress[field_Name_Turbine].unique()
-        for name in unique_turbines:
-            individual_fig = make_subplots(rows=1, cols=1)
-            # Add all turbines in grey
-            for turbine in unique_turbines:
-                group = ress[ress[field_Name_Turbine] == turbine]
-                individual_fig.add_trace(go.Scatter(
-                    x=group[x_name], y=group[y_name], mode='lines', name=turbine, line=dict(color='lightgrey')))
-
-            # Highlight the current turbine in dark blue
-            group = ress[ress[field_Name_Turbine] == name]
-            individual_fig.add_trace(go.Scatter(
-                x=group[x_name], y=group[y_name], mode='lines', name=name, line=dict(color='darkblue')))
-
-            individual_fig.update_layout(title_text='设备名={}'.format(name))
-            individual_fig.write_image(os.path.join(
-                output_path, "all-{}.png".format(name)), scale=2)

+ 0 - 109
dataAnalysisBusiness/algorithm/cpTrendAnalyst.py

@@ -1,109 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-from plotly.subplots import make_subplots
-import plotly.graph_objects as go
-import matplotlib.pyplot as plt
-from matplotlib.ticker import MultipleLocator
-from behavior.analystExcludeRatedPower import AnalystExcludeRatedPower
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-
-class CpTrendAnalyst(AnalystExcludeRatedPower):
-    """
-    风电机组风能利用系数时序分析
-    """
-
-    def typeAnalyst(self):
-        return "cp_trend"
-
-    def turbinesAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        self.drawCpTrend(dataFrameMerge, outputAnalysisDir, confData)
-
-    def drawCpTrend(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # 检查所需列是否存在
-        required_columns = {Field_Cp, Field_YearMonthDay}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        for name, group in grouped:
-            # # 计算四分位数和IQR
-            Q1 = group[Field_Cp].quantile(0.25)
-            Q3 = group[Field_Cp].quantile(0.85)
-            IQR = Q3 - Q1
-            # 定义离群值的范围
-            lower_bound = Q1 - 1.5 * IQR
-            upper_bound = Q3 + 1.5 * IQR
-
-            # 筛选掉离群值
-            filtered_group = group[(group[Field_Cp] >= lower_bound) & (
-                group[Field_Cp] <= upper_bound)]
-
-            # 创建箱线图
-            fig = go.Figure()
-
-            fig.add_trace(go.Box(
-                x=filtered_group[Field_YearMonthDay],  # 设置x轴数据为日期
-                y=filtered_group[Field_Cp],  # 设置y轴数据为风能利用系数
-                # boxpoints='outliers',  # 显示异常值(偏离值),不显示数据的所有点(只显示异常值)
-                boxpoints=False,  # 不显示偏离值
-                marker=dict(color='lightgoldenrodyellow',
-                            size=1),  # 设置偏离值的颜色和大小
-                line=dict(color='lightgray', width=2),  # 设置箱线和须线的颜色为灰色,粗细为2
-                fillcolor='rgba(200, 200, 200, 0.5)',  # 设置箱体的填充颜色和透明度
-                name='Cp'  # 图例名称
-            ))
-
-            # 对于每个箱线图的中位数,绘制一个蓝色点
-            medians = filtered_group.groupby(filtered_group[Field_YearMonthDay])[
-                Field_Cp].median()
-            fig.add_trace(go.Scatter(
-                x=medians.index,
-                y=medians.values,
-                mode='markers',
-                marker=dict(color='orange', size=3),
-                name='Median Cp'  # 中位数标记的图例名称
-            ))
-
-            # 设置图表的标题和轴标签
-            fig.update_layout(
-                title={
-                    'text': f'Cp Trend Turbine Name {name}',
-                    # 'y': 1,
-                    'x': 0.5,
-                    # 'xanchor': 'center',
-                    # 'yanchor': 'top'
-                },
-                xaxis_title='Time',
-                yaxis_title='Cp',
-                xaxis=dict(
-                    tickmode='auto',  # 自动设置x轴刻度,以适应日期数据
-                    tickformat='%Y-%m-%d',  # 设置x轴时间格式
-                    showgrid=True,  # 显示网格线
-                    gridcolor='lightgray',  # setting y-axis gridline color to black
-                    tickangle=-45,
-                    linecolor='black',  # 设置y轴坐标系线颜色为黑色
-                    ticklen=5,  # 设置刻度线的长度
-                ),
-                yaxis=dict(
-                    dtick=confData.graphSets["cp"]["step"] if not self.common.isNone(
-                        confData.graphSets["cp"]["step"]) else 0.5,  # 设置y轴刻度间隔为0.1
-                    range=[confData.graphSets["cp"]["min"] if not self.common.isNone(
-                        confData.graphSets["cp"]["min"]) else 0, confData.graphSets["cp"]["max"] if not self.common.isNone(confData.graphSets["cp"]["max"]) else 2],  # 设置y轴的范围从0到1
-                    showgrid=True,  # 显示网格线
-                    gridcolor='lightgray',  # setting y-axis gridline color to black
-                    linecolor='black',  # 设置y轴坐标系线颜色为黑色
-                    ticklen=5,  # 设置刻度线的长度
-                ),
-                paper_bgcolor='white',  # 设置纸张背景颜色为白色
-                plot_bgcolor='white',  # 设置图表背景颜色为白色
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-            )
-
-            # 保存图像
-            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            fig.write_image(output_file, scale=2)

+ 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()
-
-
-

+ 0 - 369
dataAnalysisBusiness/algorithm/dataProcessor.py

@@ -1,369 +0,0 @@
-import os
-from datetime import datetime
-import concurrent.futures
-import numpy as np
-import pandas as pd
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-from behavior.baseAnalyst import BaseAnalyst
-from behavior.analyst import Analyst
-from common.commonBusiness import CommonBusiness
-from algorithm.dataMarker import DataMarker
-
-
-class DataProcessor:
-    def __init__(self):
-        self.common = CommonBusiness()
-        self._baseAnalysts = []
-        self._analysts = []
-
-    def attachBaseAnalyst(self, analyst: BaseAnalyst):
-        if analyst not in self._analysts:
-            self._baseAnalysts.append(analyst)
-
-    def detachBaseAnalyst(self, analyst: BaseAnalyst):
-        try:
-            self._baseAnalysts.remove(analyst)
-        except ValueError:
-            pass
-
-    def attach(self, analyst: Analyst):
-        if analyst not in self._analysts:
-            self._analysts.append(analyst)
-
-    def detach(self, analyst: Analyst):
-        try:
-            self._analysts.remove(analyst)
-        except ValueError:
-            pass
-
-    def turbineNotify(self,
-                      dataFrameOfTurbine: pd.DataFrame,
-                      confData: ConfBusiness,
-                      turbineName):
-        for analyst in self._analysts:
-            outputAnalysisDir = analyst.getOutputAnalysisDir()
-
-            outputFilePath = r"{}/{}{}".format(
-                outputAnalysisDir, turbineName, CSVSuffix)
-
-            analyst.analysisOfTurbine(
-                dataFrameOfTurbine, outputAnalysisDir, outputFilePath, confData, turbineName)
-
-    def turbinesNotify(self, dataFrameOfTurbines: pd.DataFrame,  confData: ConfBusiness):
-        for analyst in self._analysts:
-            outputAnalysisDir = analyst.getOutputAnalysisDir()
-            analyst.analysisOfTurbines(
-                dataFrameOfTurbines, outputAnalysisDir, confData)
-
-    def baseAnalystTurbineNotify(self,
-                                 dataFrameOfTurbine: pd.DataFrame,
-                                 confData: ConfBusiness,
-                                 turbineName):
-        for analyst in self._baseAnalysts:
-            outputAnalysisDir = analyst.getOutputAnalysisDir()
-
-            outputFilePath = r"{}/{}{}".format(
-                outputAnalysisDir, turbineName, CSVSuffix)
-
-            analyst.analysisOfTurbine(
-                dataFrameOfTurbine, outputAnalysisDir, outputFilePath, confData, turbineName)
-
-    def baseAnalystNotify(self, dataFrameOfTurbines: pd.DataFrame,  confData: ConfBusiness):
-        for analyst in self._baseAnalysts:
-            outputAnalysisDir = analyst.getOutputAnalysisDir()
-            analyst.analysisOfTurbines(
-                dataFrameOfTurbines, outputAnalysisDir, confData)
-
-    def calculateAngleIncluded(self, array1, array2):
-        """
-        计算两个相同长度角度数组中两两对应角度值的偏差。
-        结果限制在-90°到+90°之间,并保留两位小数。
-
-        参数:
-        array1 (list): 第一个角度数组
-        array2 (list): 第二个角度数组
-
-        返回:
-        list: 两两对应角度的偏差列表
-        """
-        deviations = []
-        for angle1, angle2 in zip(array1, array2):
-            # 计算原始偏差
-            deviation = angle1 - angle2
-
-            # 调整偏差,使其位于-180°到+180°范围内
-            if deviation == 0.0:
-                deviation = 0.0
-            else:
-                deviation = (deviation + 180) % 360 - 180
-
-            # 将偏差限制在-90°到+90°范围内
-            if deviation > 90:
-                deviation -= 180
-            elif deviation < -90:
-                deviation += 180
-
-            # 保留两位小数
-            deviations.append(round(deviation, 2))
-
-        return deviations
-
-    def recalculationOfIncludedAngle(self, dataFrame: pd.DataFrame, fieldAngleIncluded, fieldWindDirect, fieldNacellePos):
-        """
-        依据机舱位置(角度)、风向计算两者夹角
-        """
-        if not self.common.isNone(fieldAngleIncluded) and fieldAngleIncluded in dataFrame.columns:
-            dataFrame[Field_AngleIncluded] = dataFrame[fieldAngleIncluded]
-
-        if self.common.isNone(fieldAngleIncluded) and fieldAngleIncluded not in dataFrame.columns and fieldWindDirect in dataFrame.columns and fieldNacellePos in dataFrame.columns:
-            dataFrame[Field_AngleIncluded] = self.calculateAngleIncluded(
-                dataFrame[fieldNacellePos], dataFrame[fieldWindDirect])
-
-    def recalculationOfGeneratorSpeed(self, dataFrame: pd.DataFrame, fieldRotorSpeed, fieldGeneratorSpeed, rotationalSpeedRatio):
-        """
-        风电机组发电机转速再计算,公式:转速比=发电机转速/叶轮或主轴转速
-        """
-        if fieldGeneratorSpeed in dataFrame.columns:
-            dataFrame[Field_GeneratorSpeed] = dataFrame[fieldGeneratorSpeed]
-
-        if fieldGeneratorSpeed not in dataFrame.columns and fieldRotorSpeed in dataFrame.columns:
-            dataFrame[fieldGeneratorSpeed] = rotationalSpeedRatio * \
-                dataFrame[fieldRotorSpeed]
-
-    def recalculationOfRotorSpeed(self, dataFrame: pd.DataFrame, fieldRotorSpeed, fieldGeneratorSpeed, rotationalSpeedRatio):
-        """
-        风电机组发电机转速再计算,公式:转速比=发电机转速/叶轮或主轴转速
-        """
-        if fieldRotorSpeed not in dataFrame.columns and fieldGeneratorSpeed in dataFrame.columns:
-            dataFrame[fieldRotorSpeed] = dataFrame[fieldGeneratorSpeed] / \
-                rotationalSpeedRatio
-
-        if not self.common.isNone(fieldRotorSpeed) and fieldRotorSpeed in dataFrame.columns:
-            dataFrame[Field_RotorSpeed] = dataFrame[fieldRotorSpeed]
-
-    def recalculationOfRotorTorque(self, dataFrame: pd.DataFrame, fieldGeneratorTorque, fieldActivePower, fieldGeneratorSpeed):
-        """
-        风电机组发电机转矩计算,P的单位换成KW转矩计算公式:
-        P*1000= pi/30*T*n  
-        30000/pi*P=T*n
-        30000/3.1415926*P=T*n
-        9549.297*p=T*n  
-        其中:n为发电机转速,p为有功功率,T为转矩
-        """
-        if self.common.isNone(fieldGeneratorTorque) and fieldActivePower in dataFrame.columns and fieldGeneratorSpeed in dataFrame.columns:
-            dataFrame[Field_GeneratorTorque] = 9549.297 * \
-                dataFrame[fieldActivePower]/dataFrame[fieldGeneratorSpeed]
-
-        if fieldGeneratorTorque in dataFrame.columns:
-            dataFrame[Field_GeneratorTorque] = dataFrame[fieldGeneratorTorque]
-
-    def recalculation(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        """
-        再计算数据测点
-        参数:
-        dataFrame 原始数据
-        confData  配置数据
-        """
-        self.recalculationOfGeneratorSpeed(
-            dataFrame, confData.field_rotor_speed, confData.field_gen_speed, confData.rotational_Speed_Ratio)
-        self.recalculationOfRotorSpeed(
-            dataFrame, confData.field_rotor_speed, confData.field_gen_speed, confData.rotational_Speed_Ratio)
-        self.recalculationOfRotorTorque(
-            dataFrame, confData.field_torque, confData.field_power, confData.field_gen_speed)
-        self.recalculationOfIncludedAngle(
-            dataFrame, confData.field_angle_included, confData.field_wind_dir, confData.field_nacelle_pos)
-
-        self.common.calculateTSR(dataFrame, confData)
-        self.common.calculateCp(dataFrame, confData)
-
-    def filterWithDateTime(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        dataFrame = dataFrame[(dataFrame[confData.field_turbine_time] >= confData.start_time) & (
-            dataFrame[confData.field_turbine_time] < confData.end_time)]
-
-        return dataFrame
-
-    def processDateTime(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        dataFrame["年月"] = pd.to_datetime(
-            dataFrame[confData.field_turbine_time], format="%Y-%m")
-        dataFrame['日期'] = pd.to_datetime(
-            dataFrame[confData.field_turbine_time], format="%Y-%m")
-        dataFrame['monthIntTime'] = dataFrame['日期'].apply(
-            lambda x: x.timestamp())
-
-        dataFrame[Field_YearMonth] = dataFrame[confData.field_turbine_time].dt.strftime(
-            '%Y-%m')
-
-        dataFrame[Field_YearMonthDay] = dataFrame[confData.field_turbine_time].dt.strftime(
-            '%Y-%m-%d')
-
-        if not self.common.isNone(confData.excludingMonths) and len(confData.excludingMonths) > 0:
-            # 给定的日期列表
-            date_strings = []
-            for month in confData.excludingMonths:
-                if not self.common.isNone(month):
-                    date_strings.append(month)
-
-            if len(date_strings) > 0:
-                mask = ~dataFrame[Field_YearMonth].isin(date_strings)
-
-                # 使用掩码过滤DataFrame,删除指定日期的行
-                dataFrame = dataFrame[mask]
-
-        return dataFrame
-
-    def setColumnDataType(self, dataFrame: pd.DataFrame,  confData: ConfBusiness):
-        # 选择所有的数值型列
-        dataFrame = dataFrame.convert_dtypes()
-        numeric_cols = dataFrame.select_dtypes(
-            include=['float64', 'float16']).columns
-        
-        # 将这些列转换为float32
-        dataFrame[Field_NameOfTurbine] = dataFrame[Field_NameOfTurbine].astype(str)
-
-        # 将这些列转换为float32
-        dataFrame[numeric_cols] = dataFrame[numeric_cols].astype(
-            self.common.getFloat32())
-
-        if not self.common.isNone(confData.field_turbine_time) and confData.field_turbine_time in dataFrame.columns:
-            # 首先尝试去除字符串前后的空白
-            dataFrame[confData.field_turbine_time] = dataFrame[confData.field_turbine_time].str.strip()
-            dataFrame[confData.field_turbine_time] = pd.to_datetime(
-                dataFrame[confData.field_turbine_time], format='%Y-%m-%d %H:%M:%S', errors="coerce")
-            dataFrame[confData.field_turbine_time] = dataFrame[confData.field_turbine_time].dt.strftime(
-                '%Y-%m-%d %H:%M:%S')
-            dataFrame[confData.field_turbine_time] = pd.to_datetime(
-                dataFrame[confData.field_turbine_time])
-
-            # 删除时间字段为空的行记录
-            dataFrame.dropna(
-                axis=0, subset=[confData.field_turbine_time], inplace=True)
-
-        if confData.field_wind_speed in dataFrame.columns:
-            dataFrame[confData.field_wind_speed] = dataFrame[confData.field_wind_speed].astype(
-                self.common.getFloat32())
-
-        if confData.field_wind_dir in dataFrame.columns:
-            dataFrame[confData.field_wind_dir] = dataFrame[confData.field_wind_dir].astype(
-                self.common.getFloat32())
-
-        if confData.field_angle_included in dataFrame.columns:
-            dataFrame[confData.field_angle_included] = dataFrame[confData.field_angle_included].astype(
-                self.common.getFloat32())
-
-        if confData.field_power in dataFrame.columns:
-            dataFrame[confData.field_power] = dataFrame[confData.field_power].astype(
-                self.common.getFloat32())
-
-        if confData.field_wind_dir in dataFrame.columns:
-            dataFrame[confData.field_wind_dir] = dataFrame[confData.field_wind_dir].astype(
-                self.common.getFloat32())
-
-        if confData.field_pitch_angle1 in dataFrame.columns:
-            dataFrame[confData.field_pitch_angle1] = dataFrame[confData.field_pitch_angle1].astype(
-                self.common.getFloat32())
-
-        if confData.field_pitch_angle2 in dataFrame.columns:
-            dataFrame[confData.field_pitch_angle2] = dataFrame[confData.field_pitch_angle2].astype(
-                self.common.getFloat32())
-
-        if confData.field_pitch_angle3 in dataFrame.columns:
-            dataFrame[confData.field_pitch_angle3] = dataFrame[confData.field_pitch_angle3].astype(
-                self.common.getFloat32())
-
-        if confData.field_gen_speed in dataFrame.columns:
-            dataFrame[confData.field_gen_speed] = dataFrame[confData.field_gen_speed].astype(
-                self.common.getFloat32())
-
-        if confData.field_rotor_speed in dataFrame.columns:
-            dataFrame[confData.field_rotor_speed] = dataFrame[confData.field_rotor_speed].astype(
-                self.common.getFloat32())
-            
-        if confData.field_Cabin_Vibrate_X in dataFrame.columns:
-            dataFrame[confData.field_Cabin_Vibrate_X] = dataFrame[confData.field_Cabin_Vibrate_X].astype(
-                self.common.getFloat32())
-        
-        if confData.field_Cabin_Vibrate_Y in dataFrame.columns:
-            dataFrame[confData.field_Cabin_Vibrate_Y] = dataFrame[confData.field_Cabin_Vibrate_Y].astype(
-                self.common.getFloat32())
-            
-        if confData.field_activePowerSet in dataFrame.columns:
-            dataFrame[confData.field_activePowerSet] = dataFrame[confData.field_activePowerSet].astype(
-                self.common.getFloat32())
-            
-        if confData.field_activePowerAvailable in dataFrame.columns:
-            dataFrame[confData.field_activePowerAvailable] = dataFrame[confData.field_activePowerAvailable].astype(
-                self.common.getFloat32())
-
-        return dataFrame
-
-    def loadData(self, csvFilePath, confData: ConfBusiness, turbineName):
-        useColumns = self.common.getUseColumns(confData)
-        # Load the CSV, skipping the specified initial rows
-        dataFrame = pd.read_csv(csvFilePath, header=0, usecols=useColumns, skiprows=range(
-            1, confData.skip_row_number+1))
-
-        if not self.common.isNone(confData.field_turbine_name) and confData.field_turbine_name in dataFrame.columns:
-            dataFrame[Field_NameOfTurbine] = dataFrame[confData.field_turbine_name]
-        else:
-            dataFrame[Field_NameOfTurbine] = turbineName
-        # 对除了“时间”列之外的所有列进行自下而上的填充(先反转后填充)
-        # 注意:补植须要考虑业务合理性
-        # dataFrame = dataFrame.fillna(method='ffill')
-        # dataFrame = dataFrame.fillna(method='bfill')
-
-        return dataFrame
-
-    def execute(self, confData: ConfBusiness):
-        outputDataAfterFilteringDir = r"{}/{}".format(
-            confData.output_path,  "DataAfterFiltering")
-        dir.create_directory(outputDataAfterFilteringDir)
-
-        labler = DataMarker()  #类的实例化
-        
-        dataFrameMerge = pd.DataFrame()
-        for rootDir, subDirs, files in dir.list_directory(confData.input_path):
-            files = sorted(files)
-            for file in files:
-                if not file.endswith(CSVSuffix):
-                    continue
-
-                csvFilePath = os.path.join(rootDir, file)
-                print(f"current csv file path: {csvFilePath}")
-                turbineName = confData.add_W_if_starts_with_digit(file.split(confData.csvFileNameSplitStringForTurbine)[
-                    confData.index_turbine])
-
-                dataFrame = self.loadData(
-                    csvFilePath, confData, turbineName)
-                turbineName = dataFrame[Field_NameOfTurbine].loc[0]
-
-                if len(dataFrame) <= 0:
-                    print("dataFrameFilter not data.")
-                    continue
-
-                dataFrame = self.setColumnDataType(
-                    dataFrame, confData)
-
-                dataFrame = self.processDateTime(dataFrame, confData)
-
-                dataFrame = self.filterWithDateTime(dataFrame, confData)
-
-                # self.baseAnalystTurbineNotify(dataFrame,
-                #                               confData,
-                #                               turbineName)
-
-                self.recalculation(dataFrame, confData)
-
-                # dataFrame = labler.main(confData,dataFrame)
-
-                dataFrameMerge = pd.concat(
-                    [dataFrameMerge, dataFrame], axis=0, sort=False)
-
-                # dataFrame.to_csv(os.path.join(
-                #     outputDataAfterFilteringDir, "{}{}".format(turbineName,CSVSuffix)), index=False)
-                dataFrame = self.turbineNotify(dataFrame,
-                                               confData,
-                                               turbineName)
-
-        # self.baseAnalystNotify(dataFrameMergeFilter,  confData)
-        self.turbinesNotify(dataFrameMerge,  confData)

+ 0 - 327
dataAnalysisBusiness/algorithm/generatorSpeedPowerAnalyst.py

@@ -1,327 +0,0 @@
-import os
-import pandas as pd
-from datetime import datetime
-import numpy as np
-import plotly.graph_objects as go
-from plotly.subplots import make_subplots
-import plotly.express as px
-import plotly.io as pio
-import seaborn as sns
-import matplotlib.pyplot as plt
-import matplotlib.cm as cm
-from matplotlib.ticker import MultipleLocator
-from matplotlib.colors import Normalize
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-from datetime import timedelta
-
-
-class GeneratorSpeedPowerAnalyst(Analyst):
-    """
-    风电机组发电机转速-有功功率分析
-    """
-
-    def typeAnalyst(self):
-        return "speed_power"
-
-    def turbinesAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # self.create_and_save_plots(
-        #     dataFrameMerge, outputAnalysisDir, confData)
-        self.drawScatter2DMonthly(
-            dataFrameMerge, outputAnalysisDir, confData)
-        self.drawScatterGraph(dataFrameMerge, outputAnalysisDir, confData)
-        self.drawScatterGraphForTurbines(
-            dataFrameMerge, outputAnalysisDir, confData)
-    """
-    def create_and_save_plots(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        x_name = 'generator_speed'
-        y_name = 'power'
-
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-        for name, group in grouped:
-            # 创建图形和坐标轴
-            fig, ax = plt.subplots()
-            cmap = cm.get_cmap('rainbow')
-
-            # 绘制散点图
-            scatter = ax.scatter(x=group[confData.field_gen_speed]*confData.value_gen_speed_multiple if not self.common.isNone(confData.value_gen_speed_multiple) else group[confData.field_gen_speed],
-                                 y=group[confData.field_power], c=group['monthIntTime'], cmap=cmap, s=5)
-
-            # 设置图形标题和坐标轴标签
-            ax.set_title(f'turbine_name={name}')
-            # 设置x轴的刻度步长
-            # 假设您想要每100个单位一个刻度
-            # 创建每100个单位一个刻度的定位器
-            loc = MultipleLocator(confData.graphSets["generatorSpeed"]["step"] if not self.common.isNone(
-                confData.graphSets["generatorSpeed"]) and not self.common.isNone(
-                confData.graphSets["generatorSpeed"]["step"]) else 200)
-            ax.xaxis.set_major_locator(loc)  # 将定位器应用到x轴上
-            ax.set_xlim(confData.graphSets["generatorSpeed"]["min"] if not self.common.isNone(
-                        confData.graphSets["generatorSpeed"]["min"]) else 1000, confData.graphSets["generatorSpeed"]["max"] if not self.common.isNone(confData.graphSets["generatorSpeed"]["max"]) else 2000)
-
-            # 创建每100个单位一个刻度的定位器
-            yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                confData.graphSets["activePower"]) and not self.common.isNone(
-                confData.graphSets["activePower"]["step"]) else 250)
-            ax.yaxis.set_major_locator(yloc)  # 将定位器应用到y轴上
-            ax.set_ylim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                        confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
-
-            ax.set_xlabel(x_name)
-            ax.set_ylabel(y_name)
-
-            # 设置颜色条
-            unique_months = len(group['年月'].unique())
-            ticks = np.linspace(group['monthIntTime'].min(
-            ), group['monthIntTime'].max(), min(unique_months, 6))  # 减少刻度数量
-            ticklabels = [datetime.fromtimestamp(
-                tick).strftime('%Y-%m') for tick in ticks]
-            norm = Normalize(group['monthIntTime'].min(),
-                             group['monthIntTime'].max())
-            sm = cm.ScalarMappable(norm=norm, cmap=cmap)
-
-            # 添加颜色条
-            cbar = fig.colorbar(sm, ax=ax)
-            cbar.set_ticks(ticks)
-            cbar.set_ticklabels(ticklabels)
-            # 旋转x轴刻度标签
-            plt.xticks(rotation=45)
-
-            plt.tight_layout()
-            plt.title(f'{Field_NameOfTurbine}={name}')
-            # 保存图片到指定路径
-            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            plt.savefig(output_file, bbox_inches='tight', dpi=120)
-            plt.close()
-
-    """
-
-    def drawScatter2DMonthlyOfTurbine(self, dataFrame: pd.DataFrame, outputAnalysisDir: str, confData: ConfBusiness, turbineName: str):
-        # 设置颜色条参数
-        dataFrame = dataFrame.sort_values(by=Field_YearMonth)
-
-        # 绘制 Plotly 散点图
-        fig = px.scatter(
-            dataFrame,
-            x=dataFrame[confData.field_gen_speed],
-            y=dataFrame[confData.field_power],
-            color=Field_YearMonth,
-            color_continuous_scale='Rainbow',  # 颜色条样式
-            labels={confData.field_gen_speed: 'Generator Speed',
-                    Field_YearMonth: 'Time', confData.field_power: 'Power'},
-        )
-
-        # 设置固定散点大小
-        fig.update_traces(marker=dict(size=3))
-
-        # 如果需要颜色轴的刻度和标签
-        # 以下是以比例方式进行色彩的可视化处理
-        fig.update_layout(
-            title={
-                "text": f'Monthly generator speed power scatter plot {turbineName}',
-                "x": 0.5
-            },
-            xaxis=dict(
-                title='Generator Speed',
-                dtick=confData.graphSets["generatorSpeed"]["step"] if not self.common.isNone(
-                    confData.graphSets["generatorSpeed"]) and not self.common.isNone(
-                    confData.graphSets["generatorSpeed"]["step"]) else 200,
-                range=[
-                    confData.graphSets["generatorSpeed"]["min"] if not self.common.isNone(
-                        confData.graphSets["generatorSpeed"]["min"]) else 1000, confData.graphSets["generatorSpeed"]["max"] if not self.common.isNone(confData.graphSets["generatorSpeed"]["max"]) else 2000
-                ],
-                tickangle=45
-            ),
-            yaxis=dict(
-                title='Power',
-                dtick=confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                    confData.graphSets["activePower"]) and not self.common.isNone(
-                    confData.graphSets["activePower"]["step"]) else 250,
-                range=[confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                    confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2],
-            ),
-            coloraxis=dict(
-                colorbar=dict(
-                    title="Time",
-                    ticks="outside",
-                    len=1,  # 设置颜色条的长度,使其占据整个图的高度
-                    thickness=20,  # 调整颜色条的宽度
-                    orientation='v',  # 设置颜色条为垂直方向
-                    tickmode='array',  # 确保刻度按顺序排列
-                    tickvals=dataFrame[Field_YearMonth].unique(
-                    ).tolist(),  # 确保刻度为唯一的年月
-                    ticktext=dataFrame[Field_YearMonth].unique(
-                    ).tolist()  # 以%Y-%m格式显示标签
-                )
-            )
-        )
-
-        # 保存图片
-        outputFilePathPNG = os.path.join(
-            outputAnalysisDir, f"{turbineName}.png")
-        pio.write_image(fig, outputFilePathPNG, format='png',
-                        width=800, height=600, scale=1)
-        
-        return fig
-
-    def drawScatterGraphOfTurbine(self, dataFrame: pd.DataFrame,  outputAnalysisDir: str, confData: ConfBusiness, turbineName: str):
-        # 创建3D散点图
-        fig = px.scatter_3d(dataFrame,
-                            x=confData.field_gen_speed,
-                            y=Field_YearMonth,
-                            z=confData.field_power,
-                            color=Field_YearMonth,
-                            labels={confData.field_gen_speed: 'Generator Speed',
-                                    Field_YearMonth: 'Time', confData.field_power: 'Power'},
-                            )
-
-        # 设置固定散点大小
-        fig.update_traces(marker=dict(size=1.5))
-
-        # 更新图形的布局
-        fig.update_layout(
-            title={
-                "text": f'Monthly generator speed power scatter plot {turbineName}',
-                "x": 0.5
-            },
-            scene=dict(
-                xaxis=dict(
-                    title='Generator Speed',
-                    dtick=confData.graphSets["generatorSpeed"]["step"] if not self.common.isNone(
-                        confData.graphSets["generatorSpeed"]["step"]) else 200,  # 设置y轴刻度间隔为0.1
-                    range=[confData.graphSets["generatorSpeed"]["min"] if not self.common.isNone(
-                        confData.graphSets["generatorSpeed"]["min"]) else 1000, confData.graphSets["generatorSpeed"]["max"] if not self.common.isNone(confData.graphSets["generatorSpeed"]["max"]) else 2000],  # 设置y轴的范围从0到1
-                    showgrid=True,  # 显示网格线
-                ),
-                yaxis=dict(
-                    title='Time',
-                    tickformat='%Y-%m',  # 日期格式,
-                    showgrid=True,  # 显示网格线
-                ),
-                zaxis=dict(
-                    title='Power',
-                    dtick=confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                        confData.graphSets["activePower"]) and not self.common.isNone(
-                        confData.graphSets["activePower"]["step"]) else 250,
-                    range=[confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                        confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2],
-                    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_title_text='Time'
-        )
-
-        # 保存图像
-        outputFileHtml = os.path.join(
-            outputAnalysisDir, "{}.html".format(turbineName))
-
-        fig.write_html(outputFileHtml)
-
-    def drawScatter2DMonthly(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-        for name, group in grouped:
-            self.drawScatter2DMonthlyOfTurbine(
-                group, outputAnalysisDir, confData, name)
-
-    def drawScatterGraph(self, dataFrame: pd.DataFrame,  outputAnalysisDir: str, confData: ConfBusiness):
-        """  
-        绘制风速-功率分布图并保存为文件。  
-
-        参数:  
-        dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
-        outputAnalysisDir (str): 分析输出目录。  
-        confData (ConfBusiness): 配置   
-        """
-        dataFrame = dataFrame[(dataFrame[confData.field_power] > 0)].sort_values(
-            by=Field_YearMonth)
-
-        grouped = dataFrame.groupby(Field_NameOfTurbine)
-
-        # 遍历每个设备的数据
-        for name, group in grouped:
-            if len(group[Field_YearMonth].unique()) > 1:
-                self.drawScatterGraphOfTurbine(
-                    group, outputAnalysisDir, confData, name)
-            else:
-                fig=self.drawScatter2DMonthlyOfTurbine(
-                    group, outputAnalysisDir, confData, name)
-                # 保存html
-                outputFileHtml = os.path.join(
-                    outputAnalysisDir, "{}.html".format(name))
-                fig.write_html(outputFileHtml)
-
-    def drawScatterGraphForTurbines(self, dataFrame: pd.DataFrame,  outputAnalysisDir, confData: ConfBusiness):
-        """  
-        绘制风速-功率分布图并保存为文件。  
-
-        参数:  
-        dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
-        outputAnalysisDir (str): 分析输出目录。  
-        confData (ConfBusiness): 配置   
-        """
-        dataFrame = dataFrame[(dataFrame[confData.field_power] > 0)].sort_values(
-            by=Field_NameOfTurbine)
-
-        # 创建3D散点图
-        fig = px.scatter_3d(dataFrame,
-                            x=confData.field_gen_speed,
-                            y=Field_NameOfTurbine,
-                            z=confData.field_power,
-                            color=Field_NameOfTurbine,
-                            labels={confData.field_gen_speed: 'Generator Speed',
-                                    Field_NameOfTurbine: 'Turbine', confData.field_power: 'Power'},
-                            )
-
-        # 设置固定散点大小
-        fig.update_traces(marker=dict(size=1.5))
-
-        # 更新图形的布局
-        fig.update_layout(
-            title={
-                "text": 'Turbine generator speed power 3D scatter plot',
-                "x": 0.5
-            },
-            scene=dict(
-                xaxis=dict(
-                    title='Generator Speed',
-                    dtick=confData.graphSets["generatorSpeed"]["step"] if not self.common.isNone(
-                        confData.graphSets["generatorSpeed"]["step"]) else 200,  # 设置y轴刻度间隔为0.1
-                    range=[confData.graphSets["generatorSpeed"]["min"] if not self.common.isNone(
-                        confData.graphSets["generatorSpeed"]["min"]) else 1000, confData.graphSets["generatorSpeed"]["max"] if not self.common.isNone(confData.graphSets["generatorSpeed"]["max"]) else 2000],  # 设置y轴的范围从0到1
-                    showgrid=True,  # 显示网格线
-                ),
-                yaxis=dict(
-                    title='Turbine',
-                    showgrid=True,  # 显示网格线
-                ),
-                zaxis=dict(
-                    title='Power',
-                    dtick=confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                        confData.graphSets["activePower"]) and not self.common.isNone(
-                        confData.graphSets["activePower"]["step"]) else 250,
-                    range=[confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                        confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2],
-                    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_title_text='Turbine'
-        )
-
-        # 保存图像
-        outputFileHtml = os.path.join(
-            outputAnalysisDir, "{}.html".format(self.typeAnalyst()))
-
-        fig.write_html(outputFileHtml)

+ 0 - 236
dataAnalysisBusiness/algorithm/generatorSpeedTorqueAnalyst.py

@@ -1,236 +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 plotly.express as px
-import seaborn as sns
-import matplotlib.pyplot as plt
-from matplotlib.ticker import MultipleLocator
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-
-class GeneratorSpeedTorqueAnalyst(Analyst):
-    """
-    风电机组发电机转速-转矩分析
-    """
-
-    def typeAnalyst(self):
-        return "speed_torque"
-
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.create_and_save_plots(
-            dataFrameMerge, outputAnalysisDir, confData)
-        self.drawScatterGraph(dataFrameMerge, outputAnalysisDir, confData)
-        self.drawScatterGraphForTurbines(
-            dataFrameMerge, outputAnalysisDir, confData)
-
-    def create_and_save_plots(self, dataFrame: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # 检查所需列是否存在
-        required_columns = {confData.field_gen_speed, Field_GeneratorTorque}
-        if not required_columns.issubset(dataFrame.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-
-        x_name = 'generator_speed'
-        y_name = 'generator_torque'
-        maxTurque = dataFrame[Field_GeneratorTorque].max()
-        grouped = dataFrame.groupby(Field_NameOfTurbine)
-        for name, group in grouped:
-            groupNew = group.copy()
-
-            if not self.common.isNone(confData.value_gen_speed_multiple):
-                groupNew[confData.field_gen_speed] = group[confData.field_gen_speed] * \
-                    confData.value_gen_speed_multiple
-            # sns.lmplot函数参数scatter_kws: 设置为{"s": 5}时,会出现颜色丢失问题;改为={"s": 5, "color": "b"}后,则造成图形风格不统一问题;
-            g = sns.lmplot(x=confData.field_gen_speed, y=Field_GeneratorTorque, data=groupNew, fit_reg=False, scatter_kws={
-                           "s": 5, "color": "b"}, legend=False, height=6, aspect=1.2)
-            # g = sns.lmplot(x=fieldGeneratorSpeed, y=Field_GeneratorTorque, data=group, fit_reg=False, scatter_kws={
-            #                "s": 5}, legend=False, height=6, aspect=1.2)
-
-            for ax in g.axes.flat:
-                # 创建每100个单位一个刻度的定位器
-                loc = MultipleLocator(confData.graphSets["generatorSpeed"]["step"] if not self.common.isNone(
-                    confData.graphSets["generatorSpeed"]) and not self.common.isNone(
-                    confData.graphSets["generatorSpeed"]["step"]) else 200)
-                ax.xaxis.set_major_locator(loc)  # 将定位器应用到x轴上
-                ax.set_xlim(confData.graphSets["generatorSpeed"]["min"] if not self.common.isNone(
-                    confData.graphSets["generatorSpeed"]["min"]) else 1000, confData.graphSets["generatorSpeed"]["max"] if not self.common.isNone(confData.graphSets["generatorSpeed"]["max"]) else 2000)
-
-                yloc = MultipleLocator(confData.graphSets["generatorTorque"]["step"] if not self.common.isNone(
-                    confData.graphSets["generatorTorque"]["step"]) else 200)
-                ax.yaxis.set_major_locator(yloc)  # 将定位器应用到y轴上
-                ax.set_ylim(confData.graphSets["generatorTorque"]["min"] if not self.common.isNone(
-                            confData.graphSets["generatorTorque"]["min"]) else 0, confData.graphSets["generatorTorque"]["max"] if not self.common.isNone(confData.graphSets["generatorTorque"]["max"]) else 2000)
-
-                ax.set_xlabel(x_name)
-                ax.set_ylabel(y_name)
-
-            plt.tight_layout()
-            plt.title(f'{Field_NameOfTurbine}={name}')
-            # 保存图片到指定路径
-            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            plt.savefig(output_file, bbox_inches='tight', dpi=120)
-            plt.close()
-
-    def drawScatterGraph(self, dataFrame: pd.DataFrame,  outputAnalysisDir, confData: ConfBusiness):
-        """  
-        绘制风速-功率分布图并保存为文件。  
-
-        参数:  
-        dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
-        outputAnalysisDir (str): 分析输出目录。  
-        confData (ConfBusiness): 配置   
-        """
-        dataFrame = dataFrame[(dataFrame[Field_GeneratorTorque] > 0)]
-        # 按设备名分组数据
-        colorsList = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
-                      '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf', '#aec7e8', '#ffbb78']
-
-        grouped = dataFrame.groupby(Field_NameOfTurbine)
-
-        # 遍历每个设备的数据
-        for name, group in grouped:
-            # 创建颜色映射,将每个年月映射到一个唯一的颜色
-            unique_months = group[Field_YearMonth].unique()
-            colors = [
-                colorsList[i % len(colorsList)] for i in range(len(unique_months))]
-            color_map = dict(zip(unique_months, colors))
-
-            # 使用go.Scatter3d创建3D散点图
-            trace = go.Scatter3d(
-                x=group[confData.field_gen_speed],
-                y=group[Field_YearMonth],
-                z=group[Field_GeneratorTorque],
-                mode='markers',
-                marker=dict(
-                    color=[color_map[month]
-                           for month in group[Field_YearMonth]],
-                    size=2,
-                    line=dict(
-                        color='rgba(0, 0, 0, 0)',  # 设置边框颜色为透明,以去掉白色边框
-                        width=0  # 设置边框宽度为0,进一步确保没有边框
-                    ),
-                    opacity=0.8  # 调整散点的透明度,增加透视效果
-                )
-            )
-
-            # 创建图形
-            fig = go.Figure(data=[trace])
-
-            # 更新图形的布局
-            fig.update_layout(
-                title={
-                    "text": f'Monthly generator speed torque 3D scatter plot {name}',
-                    "x": 0.5
-                },
-                scene=dict(
-                    xaxis=dict(
-                        title='Generator Speed',
-                        dtick=confData.graphSets["generatorSpeed"]["step"] if not self.common.isNone(
-                            confData.graphSets["generatorSpeed"]["step"]) else 200,  # 设置y轴刻度间隔为0.1
-                        range=[confData.graphSets["generatorSpeed"]["min"] if not self.common.isNone(
-                            confData.graphSets["generatorSpeed"]["min"]) else 1000, confData.graphSets["generatorSpeed"]["max"] if not self.common.isNone(confData.graphSets["generatorSpeed"]["max"]) else 2000],  # 设置y轴的范围从0到1
-                        showgrid=True,  # 显示网格线
-                    ),
-                    yaxis=dict(
-                        title='Time',
-                        tickmode='array',
-                        tickvals=unique_months,
-                        ticktext=unique_months,
-                        # dtick=500,  # 设置y轴刻度间隔
-                        # range=[0,
-                        #        group[Field_GeneratorTorque].max()],  # 设置y轴的范围
-                        showgrid=True,  # 显示网格线
-                        # categoryorder='category ascending'
-                    ),
-                    zaxis=dict(
-                        title='Generator Torque',
-                        dtick=confData.graphSets["generatorTorque"]["step"] if not self.common.isNone(
-                            confData.graphSets["generatorTorque"]["step"]) else 200,  # 设置y轴刻度间隔为0.1
-                        range=[confData.graphSets["generatorTorque"]["min"] if not self.common.isNone(
-                            confData.graphSets["generatorTorque"]["min"]) else 0, confData.graphSets["generatorTorque"]["max"] if not self.common.isNone(confData.graphSets["generatorTorque"]["max"]) else 2000],  # 设置y轴的范围从0到1
-                    )
-                ),
-                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°
-                ),
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-            )
-
-            # 保存图像
-            outputFileHtml = os.path.join(
-                outputAnalysisDir, "{}.html".format(name))
-
-            fig.write_html(outputFileHtml)
-
-    def drawScatterGraphForTurbines(self, dataFrame: pd.DataFrame,  outputAnalysisDir, confData: ConfBusiness):
-        """  
-        绘制风速-功率分布图并保存为文件。  
-
-        参数:  
-        dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
-        outputAnalysisDir (str): 分析输出目录。  
-        confData (ConfBusiness): 配置   
-        """
-        dataFrame = dataFrame[(dataFrame[confData.field_power] > 0)].sort_values(
-            by=Field_NameOfTurbine)
-
-        # 创建3D散点图
-        fig = px.scatter_3d(dataFrame,
-                            x=confData.field_gen_speed,
-                            y=Field_NameOfTurbine,
-                            z=Field_GeneratorTorque,
-                            color=Field_NameOfTurbine,
-                            labels={confData.field_gen_speed: 'Generator Speed',
-                                    Field_NameOfTurbine: 'Turbine', Field_GeneratorTorque: 'Generator Torque'},
-                            )
-
-        # 设置固定散点大小
-        fig.update_traces(marker=dict(size=1.5))
-
-        # 更新图形的布局
-        fig.update_layout(
-            title={
-                "text": 'Turbine generator speed Turque 3D scatter plot',
-                "x": 0.5
-            },
-            scene=dict(
-                xaxis=dict(
-                    title='Generator Speed',
-                    dtick=confData.graphSets["generatorSpeed"]["step"] if not self.common.isNone(
-                        confData.graphSets["generatorSpeed"]["step"]) else 200,  # 设置y轴刻度间隔为0.1
-                    range=[confData.graphSets["generatorSpeed"]["min"] if not self.common.isNone(
-                        confData.graphSets["generatorSpeed"]["min"]) else 1000, confData.graphSets["generatorSpeed"]["max"] if not self.common.isNone(confData.graphSets["generatorSpeed"]["max"]) else 2000],  # 设置y轴的范围从0到1
-                    showgrid=True,  # 显示网格线
-                ),
-                yaxis=dict(
-                    title='Turbine',
-                    showgrid=True,  # 显示网格线
-                ),
-                zaxis=dict(
-                    title='Generator Turque',
-                    dtick=confData.graphSets["generatorTorque"]["step"] if not self.common.isNone(
-                        confData.graphSets["generatorTorque"]["step"]) else 200,  # 设置y轴刻度间隔为0.1
-                    range=[confData.graphSets["generatorTorque"]["min"] if not self.common.isNone(
-                        confData.graphSets["generatorTorque"]["min"]) else 0, confData.graphSets["generatorTorque"]["max"] if not self.common.isNone(confData.graphSets["generatorTorque"]["max"]) else 2000],  # 设置y轴的范围从0到1
-                )
-            ),
-            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_title_text='Turbine',
-            margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-        )
-
-        # 保存图像
-        outputFileHtml = os.path.join(
-            outputAnalysisDir, "{}.html".format(self.typeAnalyst()))
-
-        fig.write_html(outputFileHtml)

+ 0 - 120
dataAnalysisBusiness/algorithm/minPitchAnalyst.py

@@ -1,120 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import matplotlib.pyplot as plt
-import matplotlib.cm as cm
-import matplotlib.ticker as ticker
-import plotly.express as px
-import math
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-
-class MinPitchAnalyst(Analyst):
-    """
-    风电机组最小桨距角分析
-    """
-
-    def typeAnalyst(self):
-        return "min_pitch"
-
-    def turbinesAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        self.drawTrendGraph(dataFrameMerge, outputAnalysisDir, confData)
-
-    def drawTrendGraph(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        """
-        Generates pitch angle distribution scatter plots for turbines in a wind farm using plotly.
-
-        Parameters:
-        - dataFrameMerge: pd.DataFrame, DataFrame containing turbine data.
-        - outputAnalysisDir: str, path to save the output plots.
-        - confData: ConfBusiness, configuration object containing field names.
-        """
-        # 检查所需列是否存在
-        required_columns = {Field_YearMonthDay, confData.field_pitch_angle1}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-
-        pitchAngleRate = 'pitch_angle_rate'
-        fieldPitchAngleBin = 'pitch_angle_bin'
-        # Custom color scale: Blue (high pitch_angle_rate) to Light Grey (low pitch_angle_rate)
-        custom_color_scale = [
-            (0.0, "rgb(240, 240, 240)"),  # Light grey for the lowest values
-            # (0.25, "rgb(240, 240, 240)"),  # Light grey for the lowest values
-            (0.5, "rgba(55.0, 135.0, 192.33333333333334, 1.0)"),  # Medium blue-grey
-            # (0.75, "rgba(55.0, 135.0, 192.33333333333334, 1.0)"),  # Medium blue-grey
-            # Dark blue for the highest values
-            (1.0, "rgba(55.0, 135.0, 192.33333333333334, 1.0)")
-        ]
-        # Group data by turbine identifier
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        for name, group in grouped:
-            # Convert the date column to datetime type
-            group[Field_YearMonthDay] = pd.to_datetime(
-                group[Field_YearMonthDay])
-
-            # Creating bins of 0.2 intervals for pitch angles
-            bins = pd.interval_range(start=group[confData.field_pitch_angle1].min(),
-                                     end=group[confData.field_pitch_angle1].max(
-            ) + 0.2,
-                freq=0.2, closed='right')
-            group[fieldPitchAngleBin] = pd.cut(
-                group[confData.field_pitch_angle1], bins=bins)
-            group[fieldPitchAngleBin] = group[fieldPitchAngleBin].apply(
-                lambda x: x.left)  # 提取每个区间的左端点作为值
-            # Calculate the pitch angle rate within each day
-            df = group.groupby([Field_YearMonthDay, confData.field_pitch_angle1]
-                               ).size().reset_index(name='count')
-            # df = group.groupby([Field_YearMonthDay, fieldPitchAngleBin]
-            #                    ).size().reset_index(name='count')
-            total_counts = group.groupby(
-                Field_YearMonthDay).size().reset_index(name='total_count')
-            df = df.merge(total_counts, on=Field_YearMonthDay)
-            df[pitchAngleRate] = df['count'] / df['total_count'] * 100
-            # df[pitchAngleRate] = (df['count'] / df['total_count']).apply(lambda x: x ** 0.5)*100
-
-            # Plotting using plotly
-            fig = px.scatter(df,
-                             x=Field_YearMonthDay,
-                             y=confData.field_pitch_angle1,  # 桨距角不分仓方式
-                             #  y=fieldPitchAngleBin,  # 桨距角分仓方式
-                             size='count',
-                             color=pitchAngleRate,
-                             #  color_continuous_scale='Blues',
-                             color_continuous_scale=custom_color_scale
-                             )
-
-            # Set date format on x-axis
-            fig.update_xaxes(
-                title='Time', tickformat='%Y-%m-%d', tickangle=-45)
-
-            fig.update_yaxes(title='Pitch Angle',
-                             dtick=confData.graphSets["pitchAngle"]["step"] if not self.common.isNone(
-                                 confData.graphSets["pitchAngle"]["step"]) else 2,  # 设置y轴刻度间隔为0.1
-                             range=[confData.graphSets["pitchAngle"]["min"] if not self.common.isNone(
-                                 confData.graphSets["pitchAngle"]["min"]) else -2, confData.graphSets["pitchAngle"]["max"] if not self.common.isNone(confData.graphSets["pitchAngle"]["max"]) else 28],  # 设置y轴的范围从0到1
-                             )
-
-            # Customizing legend
-            fig.update_layout(
-                title={
-                    "text": f'Pitch Angle Distribution for {name}',
-                    "x": 0.5
-                },
-                coloraxis_colorbar=dict(title='Rate'),
-                margin=dict(t=50, b=10),  # t为顶部(top)间距,b为底部(bottom)间距
-                # plot_bgcolor='rgb(240, 240, 240)' 
-            )
-
-            # Set marker size if fixed size is needed
-            # Fixed size for all points
-            fig.update_traces(marker=dict(size=3, opacity=0.5))
-
-            # Save plot
-            filePathOfImage = os.path.join(outputAnalysisDir, f"{name}.png")
-            fig.write_image(filePathOfImage, scale=2)
-
-            filePathOfHtml = os.path.join(outputAnalysisDir, f"{name}.html")
-            fig.write_html(filePathOfHtml)

+ 0 - 155
dataAnalysisBusiness/algorithm/pitchPowerAnalyst.py

@@ -1,155 +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 behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-
-class PitchPowerAnalyst(Analyst):
-    """
-    风电机组变桨-功率分析
-    """
-
-    def typeAnalyst(self):
-        return "pitch_power"
-
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.plot_power_pitch_angle(
-            dataFrameMerge, outputAnalysisDir, confData)
-        self.drawScatterGraph(dataFrameMerge, outputAnalysisDir, confData)
-
-    def plot_power_pitch_angle(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        x_name = 'power'
-        y_name = 'pitch_angle'
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-        print("self.ratedPower {}".format(confData.rated_power))
-        # 遍历每个设备并绘制散点图
-        for name, group in grouped:
-            # sns.lmplot函数参数scatter_kws: 设置为{"s": 5}时,会出现颜色丢失问题;
-            g = sns.lmplot(x=confData.field_power, y=confData.field_pitch_angle1, data=group,
-                           fit_reg=False, scatter_kws={"s": 5, "color": "b"}, legend=False, height=6, aspect=1.2)
-            # g = sns.lmplot(x=confData.field_power, y=confData.field_pitch_angle1, data=group,
-            #                fit_reg=False, scatter_kws={"s": 5}, legend=False, height=6, aspect=1.2)
-
-            # 设置x轴和y轴的刻度
-            for ax in g.axes.flat:
-                ax.xaxis.set_major_locator(MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                    confData.graphSets["activePower"]) and not self.common.isNone(
-                    confData.graphSets["activePower"]["step"]) else 250))
-                ax.set_xlim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                    confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
-
-                ax.yaxis.set_major_locator(MultipleLocator(confData.graphSets["pitchAngle"]["step"] if not self.common.isNone(
-                    confData.graphSets["pitchAngle"]["step"]) else 2))
-                ax.set_ylim(confData.graphSets["pitchAngle"]["min"] if not self.common.isNone(
-                    confData.graphSets["pitchAngle"]["min"]) else -2, confData.graphSets["pitchAngle"]["max"] if not self.common.isNone(confData.graphSets["pitchAngle"]["max"]) else 28)
-
-                ax.set_xlabel(x_name)
-                ax.set_ylabel(y_name)
-
-            # 设置x轴刻度值旋转角度为45度
-            plt.tick_params(axis='x', rotation=45)
-            # 调整布局和设置标题
-            plt.tight_layout()
-            plt.title(f'{Field_NameOfTurbine}={name}')
-
-            # 保存图像并关闭绘图窗口
-            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            plt.savefig(output_file, bbox_inches='tight', dpi=120)
-            plt.close()
-
-    def drawScatterGraph(self, dataFrame: pd.DataFrame,  outputAnalysisDir, confData: ConfBusiness):
-        """  
-        绘制变桨-功率分布图并保存为文件。  
-
-        参数:  
-        dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
-        outputAnalysisDir (str): 分析输出目录。  
-        confData (ConfBusiness): 配置   
-        """
-        # 按设备名分组数据
-        colorsList = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
-                      '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf', '#aec7e8', '#ffbb78']
-        grouped = dataFrame.groupby(Field_NameOfTurbine)
-
-        # 遍历每个设备的数据
-        for name, group in grouped:
-            # 创建颜色映射,将每个年月映射到一个唯一的颜色
-            unique_months = group[Field_YearMonth].unique()
-            colors = [
-                colorsList[i % 12] for i in range(len(unique_months))]
-            color_map = dict(zip(unique_months, colors))
-
-            # 使用go.Scatter3d创建3D散点图
-            trace = go.Scatter3d(
-                x=group[confData.field_pitch_angle1],
-                y=group[Field_YearMonth],
-                z=group[confData.field_power],
-                mode='markers',
-                marker=dict(
-                    color=[color_map[month]
-                           for month in group[Field_YearMonth]],
-                    size=1.5,
-                    line=dict(
-                        color='rgba(0, 0, 0, 0)',  # 设置边框颜色为透明,以去掉白色边框
-                        width=0  # 设置边框宽度为0,进一步确保没有边框
-                    ),
-                    opacity=0.8  # 调整散点的透明度,增加透视效果
-                )
-            )
-
-            # 创建图形
-            fig = go.Figure(data=[trace])
-
-            # 更新图形的布局
-            fig.update_layout(
-                title={
-                    "text": f'Monthly pitch to power 3D scatter plot {name}',
-                    "x": 0.5
-                },
-                scene=dict(
-                    xaxis=dict(
-                        title='Pitch Angle',
-                        dtick=confData.graphSets["pitchAngle"]["step"] if not self.common.isNone(
-                            confData.graphSets["pitchAngle"]["step"]) else 2,  # 设置y轴刻度间隔为0.1
-                        range=[confData.graphSets["pitchAngle"]["min"] if not self.common.isNone(
-                            confData.graphSets["pitchAngle"]["min"]) else -2, confData.graphSets["pitchAngle"]["max"] if not self.common.isNone(confData.graphSets["pitchAngle"]["max"]) else 28],  # 设置y轴的范围从0到1
-                        showgrid=True,  # 显示网格线
-                    ),
-                    yaxis=dict(
-                        title='Time',
-                        tickmode='array',
-                        tickvals=unique_months,
-                        ticktext=unique_months,
-                        showgrid=True,  # 显示网格线
-                        categoryorder='category ascending'
-                    ),
-                    zaxis=dict(
-                        title='Power',
-                        dtick=confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                            confData.graphSets["activePower"]) and not self.common.isNone(
-                            confData.graphSets["activePower"]["step"]) else 250,
-                        range=[confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                            confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2],
-                    )
-                ),
-                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°
-                ),
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-            )
-
-            # 保存图像
-            outputFileHtml = os.path.join(
-                outputAnalysisDir, "{}.html".format(name))
-
-            fig.write_html(outputFileHtml)

+ 0 - 98
dataAnalysisBusiness/algorithm/pitchPowerWindSpeedAnalyst.py

@@ -1,98 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import plotly.graph_objects as go
-from plotly.subplots import make_subplots
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-import plotly.offline as offline
-
-
-class PitchPowerWindSpeedAnalyst(Analyst):
-
-    def typeAnalyst(self):
-        return "pitch_power_windspeed"
-
-    # def filterCommon(self,dataFrame:pd.DataFrame, confData:ConfBusiness):
-    #     dataFrame=super().filterCommon(dataFrame,confData)
-    #     dataFrame=dataFrame[(dataFrame[confData.field_power]>=1350) & (dataFrame[confData.field_power]<=1500)]
-    #     return dataFrame
-
-    def turbinesAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        self.drawGraph(dataFrameMerge, outputAnalysisDir, confData)
-
-    def drawGraph(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        """
-        绘制3D散点图。
-
-        参数:
-        df: pandas.DataFrame。
-
-        返回:
-        一个Plotly图形对象。
-        """
-        # 检查所需列是否存在
-        required_columns = {confData.field_pitch_angle1,
-                            confData.field_power, confData.field_wind_speed}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        for name, group in grouped:
-            layout = go.Layout(
-                title={
-                    "text": f'3D scatter plot: Wind Speed vs. Pitch Angle vs. Power {name}',
-                    "x": 0.5
-                },
-                scene=dict(
-                    xaxis=dict(
-                        title='Wind Speed',
-                        dtick=2,  # 设置轴刻度间隔
-                        range=[0,
-                               26],  # 设置轴的范围
-                    ),
-                    yaxis=dict(
-                        title='Pitch Angle',
-                        dtick=confData.graphSets["pitchAngle"]["step"] if not self.common.isNone(
-                            confData.graphSets["pitchAngle"]["step"]) else 2,  # 设置y轴刻度间隔为0.1
-                        range=[confData.graphSets["pitchAngle"]["min"] if not self.common.isNone(
-                            confData.graphSets["pitchAngle"]["min"]) else -2, confData.graphSets["pitchAngle"]["max"] if not self.common.isNone(confData.graphSets["pitchAngle"]["max"]) else 28],  # 设置y轴的范围从0到1
-                    ),
-                    zaxis=dict(
-                        title='Power',
-                        dtick=confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                            confData.graphSets["activePower"]) and not self.common.isNone(
-                            confData.graphSets["activePower"]["step"]) else 250,
-                        range=[confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                            confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2],
-                    )
-                ),
-                # t为顶部(top)间距,b为底部(bottom)间距
-                margin=dict(t=50, b=10)
-            )
-
-            # 创建 3D 散点图
-            fig = go.Figure(data=[go.Scatter3d(
-                x=group[confData.field_wind_speed],
-                y=group[confData.field_pitch_angle1],
-                z=group[confData.field_power],
-                mode='markers',  # 设置模式为 markers,表示绘制散点图
-                marker=dict(
-                    size=1.5,  # 设置散点的大小
-                    # 你还可以设置其他属性,如颜色、透明度等
-                    # color='blue',
-                    # opacity=0.8
-                )
-            )], layout=layout)  # 假设 layout 已经定义好了
-
-            # 保存html
-            outputFileHtml = os.path.join(outputAnalysisDir, f"{name}.html")
-            fig.write_html(outputFileHtml)
-            # 保存图表为HTML文件
-            # offline.plot(fig, filename=outputFileHtml, auto_open=False)
-            # 保存图像
-            # output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            # fig.write_image(output_file)

+ 0 - 91
dataAnalysisBusiness/algorithm/pitchTSRCpAnalyst.py

@@ -1,91 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import plotly.graph_objects as go
-from plotly.subplots import make_subplots
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-import plotly.offline as offline
-
-
-class PitchTSRCpAnalyst(Analyst):
-
-    def typeAnalyst(self):
-        return "pitch_tsr_cp"
-
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.windRoseAnalysis(dataFrameMerge, outputAnalysisDir, confData)
-
-    def windRoseAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        """
-        绘制3D曲面图。
-
-        参数:
-        df: pandas.DataFrame, 必须包含confData.field_pitch_angle1, Field_TSR, 和 Field_Cp这三个字段。
-
-        返回:
-        一个Plotly图形对象。
-        """
-        # 检查所需列是否存在
-        required_columns = {confData.field_pitch_angle1, Field_TSR, Field_Cp}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        for name, group in grouped:
-            layout = go.Layout(
-                title={
-                    "text": f'3D scatter plot: Cp vs. Pitch Angle vs. TSR {name}',
-                    "x": 0.5
-                },
-                scene=dict(
-                    xaxis=dict(
-                        title='Pitch Angle',
-                        dtick=confData.graphSets["pitchAngle"]["step"] if not self.common.isNone(
-                            confData.graphSets["pitchAngle"]["step"]) else 2,  # 设置y轴刻度间隔为0.1
-                        range=[confData.graphSets["pitchAngle"]["min"] if not self.common.isNone(
-                            confData.graphSets["pitchAngle"]["min"]) else -2, confData.graphSets["pitchAngle"]["max"] if not self.common.isNone(confData.graphSets["pitchAngle"]["max"]) else 28],  # 设置y轴的范围从0到1
-                    ),
-                    yaxis=dict(
-                        title='TSR',
-                        dtick=confData.graphSets["tsr"]["step"] if not self.common.isNone(
-                            confData.graphSets["tsr"]["step"]) else 5,  # 设置y轴刻度间隔为0.1
-                        range=[confData.graphSets["tsr"]["min"] if not self.common.isNone(
-                            confData.graphSets["tsr"]["min"]) else 0, confData.graphSets["tsr"]["max"] if not self.common.isNone(confData.graphSets["tsr"]["max"]) else 20],  # 设置y轴的范围从0到1
-                    ),
-                    zaxis=dict(
-                        title='Cp',
-                        dtick=confData.graphSets["cp"]["step"] if not self.common.isNone(
-                            confData.graphSets["cp"]["step"]) else 0.5,  # 设置y轴刻度间隔为0.1
-                        range=[confData.graphSets["cp"]["min"] if not self.common.isNone(
-                            confData.graphSets["cp"]["min"]) else 0, confData.graphSets["cp"]["max"] if not self.common.isNone(confData.graphSets["cp"]["max"]) else 2],  # 设置y轴的范围从0到1
-                    )
-                ),
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-            )
-
-            # 创建 3D 散点图
-            fig = go.Figure(data=[go.Scatter3d(
-                x=group[confData.field_pitch_angle1],
-                y=group[Field_TSR],
-                z=group[Field_Cp],
-                mode='markers',  # 设置模式为 markers,表示绘制散点图
-                marker=dict(
-                    size=1,  # 设置散点的大小
-                    # 你还可以设置其他属性,如颜色、透明度等
-                    # color='blue',
-                    # opacity=0.8
-                )
-            )], layout=layout)  # 假设 layout 已经定义好了
-
-            # 保存html
-            outputFileHtml = os.path.join(outputAnalysisDir, f"{name}.html")
-            fig.write_html(outputFileHtml)
-            # 保存图表为HTML文件
-            # offline.plot(fig, filename=outputFileHtml, auto_open=False)
-            # 保存图像
-            # output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            # fig.write_image(output_file)

+ 0 - 106
dataAnalysisBusiness/algorithm/powerScatter2DAnalyst.py

@@ -1,106 +0,0 @@
-import os
-from datetime import datetime
-import pandas as pd
-import numpy as np
-import pandas as pd
-import matplotlib.pyplot as plt
-import matplotlib.cm as cm
-from matplotlib.ticker import MultipleLocator
-from matplotlib.colors import Normalize
-import seaborn as sns
-import plotly.graph_objects as go
-from plotly.subplots import make_subplots
-from geopy.distance import geodesic
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-class PowerScatter2DAnalyst(Analyst):
-    """
-    风电机组功率曲线散点分析。
-    秒级scada数据运算太慢,建议使用分钟级scada数据
-    """
-
-    def typeAnalyst(self):
-        return "power_scatter_2D"
-
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        if len(dataFrameMerge)<=0:
-            print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
-            return
-        
-        dataFrameGuaranteePowerCurve=self.common.contractGuaranteePowerCurveData(confData.turbineGuaranteedPowerCurveFilePathCSV,confData)
-        self.drawOfPowerCurveScatter(dataFrameMerge,outputAnalysisDir,confData,dataFrameGuaranteePowerCurve)
-        
-    def drawOfPowerCurveScatter(self, dataFrame: pd.DataFrame,  outputAnalysisDir, confData: ConfBusiness,dataFrameGuaranteePowerCurve:pd.DataFrame):
-        """  
-        绘制风速-功率分布图并保存为文件。  
-
-        参数:  
-        dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
-        csvPowerCurveFilePath (str): 功率曲线文件路径。   
-        outputAnalysisDir (str): 分析输出目录。  
-        confData (ConfBusiness): 配置   
-        """
-        x_name = 'wind_speed'
-        y_name = 'power'
-
-        # 按设备名分组数据
-        grouped = dataFrame.groupby(Field_NameOfTurbine)
-
-        # 遍历每个设备的数据
-        for name, group in grouped:
-            # 创建图形和坐标轴  
-            fig, ax = plt.subplots(figsize=(12, 8), dpi=96)  
-            cmap = cm.get_cmap('rainbow')  
-            
-            # 绘制散点图  
-            scatter = ax.scatter(x=group[confData.field_wind_speed],  
-                                y=group[confData.field_power], c=group['monthIntTime'], cmap=cmap, s=5)  
-            
-            # 绘制合同功率曲线  
-            ax.plot(dataFrameGuaranteePowerCurve['风速'], dataFrameGuaranteePowerCurve['有功功率'], marker='o',  
-                    c='gray', label='Contract Guarantee Power Curve')  
-            
-            # 设置图形标题和坐标轴标签  
-            ax.set_title(f'turbine_name={name}')  
-            
-            # 设置坐标轴的主刻度定位器  
-            ax.xaxis.set_major_locator(MultipleLocator(1))   
-            ax.set_xlim(0, 26)  
-
-            # 创建每100个单位一个刻度的定位器
-            yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                confData.graphSets["activePower"]) and not self.common.isNone(
-                confData.graphSets["activePower"]["step"]) else 250)
-            ax.yaxis.set_major_locator(yloc)  # 将定位器应用到y轴上
-            ax.set_ylim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                        confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
-
-            ax.set_xlabel(x_name)  
-            ax.set_ylabel(y_name)  
-                        
-            # 显示图例,并调整位置  
-            ax.legend(loc='lower right')  
-            
-            # 设置颜色条  
-            unique_months = len(group['年月'].unique())
-            ticks = np.linspace(group['monthIntTime'].min(), group['monthIntTime'].max(), min(unique_months, 6))  # 减少刻度数量 
-            ticklabels = [datetime.fromtimestamp(tick).strftime('%Y-%m') for tick in ticks]  
-            norm = Normalize(group['monthIntTime'].min(), group['monthIntTime'].max())  
-            sm = cm.ScalarMappable(norm=norm, cmap=cmap)  
-            
-            # 添加颜色条  
-            cbar = fig.colorbar(sm, ax=ax)
-            cbar.set_ticks(ticks)  
-            cbar.set_ticklabels(ticklabels)  
-            
-            # 旋转x轴刻度标签  
-            plt.xticks(rotation=45)  
-            
-            # 保存图形为文件  
-            output_file = os.path.join(outputAnalysisDir, f"{name}-scatter.png")  
-            plt.savefig(output_file, bbox_inches='tight')  
-            
-            # 关闭图形  
-            plt.close()

+ 0 - 119
dataAnalysisBusiness/algorithm/powerScatterAnalyst.py

@@ -1,119 +0,0 @@
-import os
-from datetime import datetime
-import pandas as pd
-import numpy as np
-import pandas as pd
-import plotly.graph_objects as go
-from plotly.subplots import make_subplots
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-import plotly.express.colors as px_colors
-
-
-class PowerScatterAnalyst(Analyst):
-    """
-    风电机组功率曲线散点分析。
-    秒级scada数据运算太慢,建议使用分钟级scada数据
-    """
-
-    def typeAnalyst(self):
-        return "power_scatter"
-
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        if len(dataFrameMerge) <= 0:
-            print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
-            return
-
-        dataFrameGuaranteePowerCurve = self.dataFrameContractOfTurbine
-
-        self.drawOfPowerCurveScatter(
-            dataFrameMerge, outputAnalysisDir, confData, dataFrameGuaranteePowerCurve)
-
-    def contractGuaranteePowerCurveData(self, csvPowerCurveFilePath):
-        dataFrameGuaranteePowerCurve = pd.read_csv(
-            csvPowerCurveFilePath, encoding=charset_unify)
-
-        return dataFrameGuaranteePowerCurve
-
-    def drawOfPowerCurveScatter(self, dataFrame: pd.DataFrame,  outputAnalysisDir, confData: ConfBusiness, dataFrameGuaranteePowerCurve: pd.DataFrame):
-        """  
-        绘制风速-功率分布图并保存为文件。  
-
-        参数:  
-        dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
-        csvPowerCurveFilePath (str): 功率曲线文件路径。   
-        outputAnalysisDir (str): 分析输出目录。  
-        confData (ConfBusiness): 配置   
-        """
-        # 按设备名分组数据
-        colorsList = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
-                      '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf', '#aec7e8', '#ffbb78']
-        grouped = dataFrame.groupby(Field_NameOfTurbine)
-
-        # 遍历每个设备的数据
-        for name, group in grouped:
-            # 创建颜色映射,将每个年月映射到一个唯一的颜色
-            unique_months = group[Field_YearMonth].unique()
-            colors = [
-                colorsList[i % 12] for i in range(len(unique_months))]
-            color_map = dict(zip(unique_months, colors))
-
-            # 使用go.Scatter3d创建3D散点图
-            trace = go.Scatter3d(
-                x=group[confData.field_wind_speed],
-                y=group[Field_YearMonth],
-                z=group[confData.field_power],
-                mode='markers',
-                marker=dict(
-                    color=[color_map[month]
-                           for month in group[Field_YearMonth]],
-                    size=1.5,
-                    line=dict(
-                        color='rgba(0, 0, 0, 0)',  # 设置边框颜色为透明,以去掉白色边框
-                        width=0  # 设置边框宽度为0,进一步确保没有边框
-                    ),
-                    opacity=0.8  # 调整散点的透明度,增加透视效果
-                )
-            )
-
-            # 创建图形
-            fig = go.Figure(data=[trace])
-
-            # 更新图形的布局
-            fig.update_layout(
-                title={
-                    "text": f'Monthly power 3D scatter plot {name}',
-                    "x": 0.5
-                },
-                scene=dict(
-                    xaxis=dict(title='Wind Speed'),
-                    yaxis=dict(
-                        title='Time',
-                        tickmode='array',
-                        tickvals=unique_months,
-                        ticktext=unique_months,
-                        categoryorder='category ascending'
-                    ),
-                    zaxis=dict(
-                        title='Power',
-                        dtick=confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                            confData.graphSets["activePower"]) and not self.common.isNone(
-                            confData.graphSets["activePower"]["step"]) else 250,
-                        range=[confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                            confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2],
-                    )
-                ),
-                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°
-                ),
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-            )
-
-            # 保存图像
-            outputFileHtml = os.path.join(
-                outputAnalysisDir, "{}.html".format(name))
-
-            fig.write_html(outputFileHtml)

+ 0 - 375
dataAnalysisBusiness/algorithm/temperatureLargeComponentsAnalyst.py

@@ -1,375 +0,0 @@
-import os
-import pandas as pd
-from matplotlib.ticker import MultipleLocator
-import numpy as np
-import pandas as pd
-import plotly.graph_objects as go
-import matplotlib.pyplot as plt
-import matplotlib.ticker as ticker
-import seaborn as sns
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-
-class Generator:
-    def __init__(self) -> None:
-        self.fieldTemperatorOfDEBearing = None
-        self.fieldTemperatorOfNDEBearing = None
-
-
-class TemperatureLargeComponentsAnalyst(Analyst):
-    """
-    风电机组大部件温升分析
-    """
-    fieldPowerFloor = 'power_floor'
-
-    def typeAnalyst(self):
-        return "temperature_large_components"
-
-    def getUseColumns(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        # Convert the string list of temperature columns into a list
-        temperature_cols = self.getLargeComponentTemperatureColumns(confData)
-        # 获取非全为空的列名
-        non_empty_cols = self.getNoneEmptyFields(dataFrame, temperature_cols)
-
-        useCols = []
-        useCols.append(confData.field_turbine_time)
-        useCols.append(confData.field_power)
-
-        if not self.common.isNone(confData.field_env_temp) and confData.field_env_temp in dataFrame.columns:
-            useCols.append(confData.field_env_temp)
-
-        if not self.common.isNone(confData.field_nacelle_temp) and confData.field_nacelle_temp in dataFrame.columns:
-            useCols.append(confData.field_nacelle_temp)
-
-        useCols.extend(non_empty_cols)
-
-        return useCols
-
-    def getLargeComponentTemperatureColumns(self, confData: ConfBusiness):
-        if self.common.isNone(confData.field_temperature_large_components):
-            return []
-        temperature_cols = confData.field_temperature_large_components.split(
-            ',')
-        return temperature_cols
-
-    # def filterCustomForTurbine(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-    #     useCols = self.getUseColumns(dataFrame,confData)
-    #     # 清洗数据
-    #     dataFrameCustome = dataFrame[useCols].copy()
-    #     dataFrameCustome = dataFrameCustome.dropna(axis=1, how='all')
-    #     dataFrameCustome = dataFrameCustome.dropna(axis=0, subset=useCols)
-
-    #     return dataFrameCustome
-
-    # def turbineAnalysis(self,
-    #                     dataFrame,
-    #                     outputAnalysisDir,
-    #                     outputFilePath,
-    #                     confData: ConfBusiness,
-    #                     turbineName):
-    #     self.temp_power(dataFrame, outputFilePath, confData)
-
-    def getNoneEmptyFields(self, dataFrame, temperatureFields):
-        # 使用set和列表推导式来获取在DataFrame中存在的字段
-        existing_fields = [
-            field for field in temperatureFields if field in dataFrame.columns]
-        # 检查指定列中非全为空的列
-        non_empty_columns = dataFrame[existing_fields].apply(
-            lambda x: x.notnull().any(), axis=0)
-        # 获取非全为空的列名
-        noneEmptyFields = non_empty_columns[non_empty_columns].index.tolist()
-        return noneEmptyFields
-
-    def dataReprocess(self, dataFrame: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # 获取非全为空的列名
-        non_empty_cols = self.getUseColumns(dataFrame, confData)
-        dataFrame = dataFrame.dropna(subset=non_empty_cols)
-        # Calculate 'power_floor'
-        dataFrame[self.fieldPowerFloor] = (
-            dataFrame[confData.field_power] / 10).astype(int) * 10
-
-        # Initialize an empty DataFrame for aggregation
-        # agg_dict = {col: 'mean' for col in non_empty_cols}
-        agg_dict = {col: 'median' for col in non_empty_cols}
-
-        # Group by 'power_floor' and aggregate
-        grouped = dataFrame.groupby(
-            [self.fieldPowerFloor, Field_NameOfTurbine]).agg(agg_dict).reset_index()
-
-        # Sort by 'power_floor'
-        grouped.sort_values(
-            [self.fieldPowerFloor, Field_NameOfTurbine], inplace=True)
-
-        return grouped
-
-    def turbinesAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # self.plot_temperature_distribution(dataFrameMerge,
-        #     outputAnalysisDir, confData, confData.field_temperature_large_components)
-        dataFrame = self.dataReprocess(
-            dataFrameMerge, outputAnalysisDir, confData)
-        self.drawTemperatureGraph(dataFrame, outputAnalysisDir, confData)
-
-    def drawTemperatureGraph(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        """
-        大部件温度传感器分析
-        """
-        y_name = 'temperature'
-
-        outputDir = os.path.join(outputAnalysisDir, "GeneratorTemperature")
-        dir.create_directory(outputDir)
-
-        columns = confData.field_temperature_large_components.split(',')
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        # Create output directories if they don't exist
-        for column in columns:
-            if not column in dataFrameMerge.columns:
-                continue
-
-            sns.set_palette('deep')
-
-            outputPath = os.path.join(outputAnalysisDir, column)
-            dir.create_directory(outputPath)
-
-            # 绘制当前温度测点的,所有机组折线图
-            fig, ax = plt.subplots()
-            ax = sns.lineplot(x=self.fieldPowerFloor, y=column, data=dataFrameMerge,
-                              hue=Field_NameOfTurbine)
-
-            # 创建每100个单位一个刻度的定位器
-            yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                confData.graphSets["activePower"]) and not self.common.isNone(
-                confData.graphSets["activePower"]["step"]) else 250)
-            ax.xaxis.set_major_locator(yloc)  # 将定位器应用到y轴上
-            ax.set_xlim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                        confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
-
-            ax.yaxis.set_major_locator(ticker.MultipleLocator(20))
-            ax.set_ylim(0, 100)
-            ax.set_xlabel(self.fieldPowerFloor)
-            ax.set_ylabel(y_name)
-            ax.set_title('Temperature-Distribute')
-
-            # 获取线对象的句柄和标签
-            lines, labels = ax.get_legend_handles_labels()
-            # 排序标签(例如,按照字母顺序)
-            sorted_labels = sorted(labels)
-            sorted_lines = [l for l, lbl in zip(
-                lines, labels) if lbl in sorted_labels]
-            # 创建新的图例
-            ax.legend(sorted_lines, sorted_labels, bbox_to_anchor=(
-                1.02, 0.5), loc='center left', ncol=2, borderaxespad=0.)
-
-            # plt.legend(bbox_to_anchor=(1.02, 0.5),
-            #             loc='center left', ncol=2, borderaxespad=0.)
-            plt.savefig(os.path.join(outputPath, "{}.png".format(
-                column)), bbox_inches='tight', dpi=120)
-            plt.close()
-
-            for name, group in grouped:
-                # Write to CSV
-                # csvFileOfTurbine=os.path.join(outputAnalysisDir,f'{name}{CSVSuffix}')
-                # group.to_csv(csvFileOfTurbine,index=False)
-                color = ["lightgrey"] * \
-                    len(dataFrameMerge[Field_NameOfTurbine].unique())
-                fig, ax = plt.subplots()
-                ax = sns.lineplot(x=self.fieldPowerFloor, y=column, data=dataFrameMerge, hue=Field_NameOfTurbine,
-                                  palette=sns.set_palette(color), legend=False)
-                ax = sns.lineplot(x=self.fieldPowerFloor, y=column, data=group,
-                                  color='darkblue', legend=False)
-                
-                ax.set_title('turbine_name={}'.format(name))
-
-                ax.yaxis.set_major_locator(ticker.MultipleLocator(20))
-                ax.set_ylim(0, 100)
-                ax.set_ylabel(y_name)
-
-                ax.xaxis.set_major_locator(ticker.MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                    confData.graphSets["activePower"]) and not self.common.isNone(
-                    confData.graphSets["activePower"]["step"]) else 250))
-                ax.set_xlim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                    confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
-                ax.set_xlabel(self.fieldPowerFloor)
-
-                plt.savefig(os.path.join(outputPath, "{}.png".format(
-                    name)), bbox_inches='tight', dpi=120)
-
-                plt.close()
-
-                # 绘制每台机组发电机的,驱动轴承温度、非驱动轴承温度、发电机轴承温度BIAS、发电机轴承温度和机舱温度BIAS 均与有功功率的折线图
-                dictConf = self.getGeneratorTemperatureConf(confData)
-                if not self.common.isNone(dictConf):
-                    self.drawGeneratorTemperature(
-                        group, confData, dictConf["yAxisDE"], dictConf["yAxisNDE"], dictConf["diffTemperature"], self.fieldPowerFloor, name, outputDir)
-
-    def plot_temperature_distribution(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness, field_temperature_large_componts, encoding='utf-8'):
-        """
-        Generates Cp distribution plots for turbines in a wind farm.
-
-        Parameters:
-        - csvFileDirOfCp: str, path to the directory containing input CSV files.
-        - farm_name: str, name of the wind farm.
-        - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
-        """
-        field_Name_Turbine = "turbine_name"
-        x_name = 'power_floor'
-        y_name = 'temperature'
-
-        outputDir = os.path.join(outputAnalysisDir, "GeneratorTemperature")
-        dir.create_directory(outputDir)
-        sns.set_palette('deep')
-
-        columns = field_temperature_large_componts.split(',')
-        # Create output directories if they don't exist
-        for column in columns:
-            type_name = '{}'.format(column)
-            output_path = os.path.join(outputAnalysisDir, type_name)
-            os.makedirs(output_path, exist_ok=True)
-            print("current column {}".format(column))
-
-            # Initialize DataFrame to store concatenated data
-            res = pd.DataFrame()
-
-            # Iterate over files in the input path
-            for root, dir_names, file_names in dir.list_directory(outputAnalysisDir):
-                for file_name in file_names:
-
-                    if not file_name.endswith(CSVSuffix):
-                        continue
-
-                    print(os.path.join(root, file_name))
-                    frame = pd.read_csv(os.path.join(
-                        root, file_name), encoding=encoding)
-
-                    if column not in frame.columns:
-                        continue
-
-                    # 获取输出文件名(不含split_way之后的部分)
-                    turbineName = file_name.split(CSVSuffix)[0]
-                    # 添加设备名作为新列
-                    frame[field_Name_Turbine] = confData.add_W_if_starts_with_digit(
-                        turbineName)
-
-                    dictConf = self.getGeneratorTemperatureConf(confData)
-                    if not self.common.isNone(dictConf):
-                        self.drawGeneratorTemperature(
-                            frame, dictConf["yAxisDE"], dictConf["yAxisNDE"], dictConf["diffTemperature"], x_name, turbineName, outputDir)
-
-                    res = pd.concat(
-                        [res, frame.loc[:, [field_Name_Turbine, x_name, column]]], axis=0)
-
-            # Reset index and plot
-            ress = res.reset_index()
-            fig, ax2 = plt.subplots()
-            ax2 = sns.lineplot(x=x_name, y=column, data=ress,
-                               hue=field_Name_Turbine)
-            # ax2.set_xlim(-150, 2100)
-            ax2.set_xlabel(x_name)
-            ax2.set_ylabel(y_name)
-            ax2.set_title('Temperature-Distribute')
-            plt.legend(bbox_to_anchor=(1.02, 0.5),
-                       loc='center left', ncol=2, borderaxespad=0.)
-            plt.savefig(os.path.join(output_path, "{}.png".format(
-                column)), bbox_inches='tight', dpi=120)
-            plt.close()
-
-            # Plot individual device lines
-            grouped = ress.groupby(field_Name_Turbine)
-            for name, group in grouped:
-                color = ["lightgrey"] * len(ress[field_Name_Turbine].unique())
-                fig, ax = plt.subplots()
-                ax = sns.lineplot(x=x_name, y=column, data=ress, hue=field_Name_Turbine,
-                                  palette=sns.set_palette(color), legend=False)
-                ax = sns.lineplot(x=x_name, y=column, data=group,
-                                  color='darkblue', legend=False)
-                ax.set_xlabel(x_name)
-                ax.set_ylabel(y_name)
-                ax.set_title('turbine_name={}'.format(name))
-                # ax.set_xlim(-150, 2100)
-                plt.savefig(os.path.join(output_path, "{}.png".format(
-                    name)), bbox_inches='tight', dpi=120)
-
-                plt.close()
-
-    def getGeneratorTemperatureConf(self, confData: ConfBusiness):
-        if self.common.isNone(confData.temperature_Generator) or self.common.isNone(confData.temperature_Generator["yAxisDE"]) or self.common.isNone(confData.temperature_Generator["yAxisNDE"]):
-            return None
-
-        return confData.temperature_Generator
-
-    def drawGeneratorTemperature(self, dataFrame: pd.DataFrame, confData: ConfBusiness, yAxisDE, yAxisNDE, diffTemperature, xAxis, turbineName, outputDir):
-        # 发电机驱动轴承温度 和 发电机非驱动轴承 温差
-        fieldBIAS_DE_NDE = 'BIAS_DE-NDE'
-        fieldBIAS_DE = 'BIAS_DE'
-        fieldBIAS_NDE = 'BIAS_NDE'
-
-        # 绘制双y轴折线图
-        fig, ax1 = plt.subplots()
-
-        # 绘制低速轴承温度和高速轴承温度
-        ax1.plot(dataFrame[xAxis], dataFrame[yAxisDE],
-                 label='DEBearingTemperature', color='blue')
-        # 计算低速轴承温度和高速轴承温度的差值
-        dataFrame[fieldBIAS_DE] = dataFrame[yAxisDE] - \
-            dataFrame[diffTemperature]
-        # 绘制温度差值
-        ax1.plot(dataFrame[xAxis], dataFrame[fieldBIAS_DE],
-                 label=fieldBIAS_DE, color='blue', linestyle=':')
-
-        ax1.plot(dataFrame[xAxis], dataFrame[yAxisNDE],
-                 label='NDEBearingTemperature', color='green')
-        # 计算低速轴承温度和高速轴承温度的差值
-        dataFrame[fieldBIAS_NDE] = dataFrame[yAxisNDE] - \
-            dataFrame[diffTemperature]
-        # 绘制温度差值
-        ax1.plot(dataFrame[xAxis], dataFrame[fieldBIAS_NDE],
-                 label=fieldBIAS_NDE, color='green', linestyle=':')
-
-        ax1.plot(dataFrame[xAxis], dataFrame[diffTemperature],
-                 label='NacelleTemperature', color='orange')
-
-        # # 创建第二个y轴
-        # ax2 = ax1.twinx()
-
-        # 计算低速轴承温度和高速轴承温度的差值
-        dataFrame[fieldBIAS_DE_NDE] = dataFrame[yAxisDE] - dataFrame[yAxisNDE]
-        # 绘制温度差值
-        ax1.plot(dataFrame[xAxis], dataFrame[fieldBIAS_DE_NDE],
-                 label=fieldBIAS_DE_NDE, color='black', linestyle='--')
-
-        # 设置y2轴的上限和下限
-        # ax2.set_ylim(-5, 5)
-        plt.axhline(y=5, ls=":", c="red")  # 添加水平直线
-        plt.axhline(y=-5, ls=":", c="red")  # 添加水平直线
-
-        # 第一个图例放在右上角
-        ax1.legend(title='Temperature & BIAS', bbox_to_anchor=(
-            1.3, 0.5), loc='center', borderaxespad=0.)
-
-        # # 第二个图例放在第一个图例稍下的位置
-        # ax1.legend(title='BIAS', bbox_to_anchor=(1.5, 1), loc='upper right', borderaxespad=0.)
-
-        # 设置x轴和y轴标签
-        ax1.set_xlabel("power")
-        ax1.set_ylabel('Bearing Temperature & BIAS')
-        # ax2.set_ylabel('Temperature BIAS')
-
-        ax1.xaxis.set_major_locator(ticker.MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-            confData.graphSets["activePower"]) and not self.common.isNone(
-            confData.graphSets["activePower"]["step"]) else 250))
-        ax1.set_xlim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
-            confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
-
-        ax1.yaxis.set_major_locator(ticker.MultipleLocator(confData.graphSets["generatorTemperature"]["step"] if not self.common.isNone(
-            confData.graphSets["generatorTemperature"]) and not self.common.isNone(
-            confData.graphSets["generatorTemperature"]["step"]) else 10))
-        ax1.set_ylim(confData.graphSets["generatorTemperature"]["min"] if not self.common.isNone(
-            confData.graphSets["generatorTemperature"]["min"]) else -20, confData.graphSets["generatorTemperature"]["max"] if not self.common.isNone(confData.graphSets["generatorTemperature"]["max"]) else 100)
-
-        plt.title(f'Generator Temperture BIAS={turbineName}')
-
-        plt.savefig(os.path.join(outputDir, "{}.png".format(
-                    turbineName)), bbox_inches='tight', dpi=120)

+ 0 - 102
dataAnalysisBusiness/algorithm/tsrTrendAnalyst.py

@@ -1,102 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import matplotlib.pyplot as plt
-import seaborn as sns
-import plotly.graph_objects as go
-from behavior.analystExcludeRatedPower import AnalystExcludeRatedPower
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-
-class TSRTrendAnalyst(AnalystExcludeRatedPower):
-    """
-    风电机组叶尖速比时序分析
-    """
-
-    def typeAnalyst(self):
-        return "tsr_trend"
-
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.drawTSRTrend(dataFrameMerge, outputAnalysisDir, confData)
-
-    def drawTSRTrend(self,dataFrameMerge:pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):        
-        # 检查所需列是否存在
-        required_columns = {Field_TSR, Field_YearMonthDay}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-        
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        for name, group in grouped:
-            # 计算四分位数和IQR
-            Q1 = group[Field_TSR].quantile(0.15)
-            Q3 = group[Field_TSR].quantile(0.90)
-            IQR = Q3 - Q1
-            # 定义离群值的范围
-            lower_bound = Q1 - 1.5 * IQR
-            upper_bound = Q3 + 1.5 * IQR
-
-            # 筛选掉离群值
-            filtered_group = group[(group[Field_TSR] >= lower_bound) & (group[Field_TSR] <= upper_bound)]
-
-            # 创建箱线图
-            fig = go.Figure()
-
-            fig.add_trace(go.Box(
-                x=filtered_group[Field_YearMonthDay],  # 设置x轴数据为日期
-                y=filtered_group[Field_TSR],  # 设置y轴数据为风能利用系数
-                # boxpoints='outliers',  # 显示异常值(偏离值),不显示数据的所有点(只显示异常值)
-                boxpoints=False,  # 不显示偏离值
-                marker=dict(color='lightgoldenrodyellow', size=1),  # 设置偏离值的颜色和大小
-                line=dict(color='lightgray', width=2),  # 设置箱线和须线的颜色为灰色,粗细为2
-                fillcolor='rgba(200, 200, 200, 0.5)',  # 设置箱体的填充颜色和透明度
-                name='TSR'  # 图例名称
-            ))
-
-            # 对于每个箱线图的中位数,绘制一个蓝色点
-            medians = filtered_group.groupby(filtered_group[Field_YearMonthDay])[Field_TSR].median()
-            fig.add_trace(go.Scatter(
-                x=medians.index,
-                y=medians.values,
-                mode='markers',
-                marker=dict(color='orange', size=3),
-                name='Median TSR'  # 中位数标记的图例名称
-            ))
-
-            # 设置图表的标题和轴标签
-            fig.update_layout(
-                title={
-                    'text': f'TSR Trend Turbine Name {name}',
-                    'x':0.5,
-                },
-                xaxis_title='time',
-                yaxis_title='TSR',                
-                xaxis=dict(
-                    tickmode='auto',  # 自动设置x轴刻度,以适应日期数据
-                    tickformat='%Y-%m-%d',  # 设置x轴时间格式
-                    showgrid=True,  # 显示网格线
-                    gridcolor='lightgray',  # setting y-axis gridline color to black
-                    tickangle=-45,
-                    linecolor='black',  # 设置y轴坐标系线颜色为黑色
-                    ticklen=5,  # 设置刻度线的长度
-                ),
-                yaxis=dict(
-                    dtick=confData.graphSets["tsr"]["step"] if not self.common.isNone(
-                        confData.graphSets["tsr"]["step"]) else 5,  # 设置y轴刻度间隔为0.1
-                    range=[confData.graphSets["tsr"]["min"] if not self.common.isNone(
-                        confData.graphSets["tsr"]["min"]) else 0, confData.graphSets["tsr"]["max"] if not self.common.isNone(confData.graphSets["tsr"]["max"]) else 20],  # 设置y轴的范围从0到1
-                    showgrid=True,  # 显示网格线
-                    gridcolor='lightgray',  # setting y-axis gridline color to black
-                    linecolor='black',  # 设置y轴坐标系线颜色为黑色
-                    ticklen=5,  # 设置刻度线的长度
-                ),  
-                paper_bgcolor='white',  # 设置纸张背景颜色为白色  
-                plot_bgcolor='white',  # 设置图表背景颜色为白色  
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-            )
-            
-            # 保存图像
-            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            fig.write_image(output_file, scale=2)

+ 0 - 101
dataAnalysisBusiness/algorithm/windDirectionFrequencyAnalyst.py

@@ -1,101 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import plotly.graph_objects as go
-from plotly.subplots import make_subplots
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-import matplotlib.pyplot as plt
-from algorithmContract.confBusiness import *
-
-
-class WindDirectionFrequencyAnalyst(Analyst):
-
-    def typeAnalyst(self):
-        return "wind_direction_frequency"
-
-    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']
-        wind_directions = np.arange(0, 360, 22.5)
-
-        colorscale = {
-            '[0,3)': 'rgba(247.0, 251.0, 255.0, 1.0)',
-            '[3,6)': 'rgba(171.33333333333334, 207.66666666666666, 229.66666666666669, 1.0)',
-            '[6,9)': 'rgba(55.0, 135.0, 192.33333333333334, 1.0)',
-            '>=9': 'rgba(8.0, 48.0, 107.0, 1.0)'
-        }
-
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        for name, group in grouped:
-            speed_bins = pd.cut(
-                group[confData.field_wind_speed], bins=bins, labels=speed_labels)
-            # 调整风向数据以使东方为0度
-            # adjusted_wind_dir = (group[confData.field_wind_dir] - 90) % 360
-            # group['风向分组'] = pd.cut(adjusted_wind_dir, bins=wind_directions, labels=wind_directions[:-1])
-            group['风向分组'] = pd.cut(
-                group[confData.field_wind_dir], bins=wind_directions, labels=wind_directions[:-1])
-
-            # 初始化子图,设定为极坐标
-            fig = make_subplots(rows=1, cols=1, specs=[[{'type': 'polar'}]])
-
-            for label in speed_labels:
-                subset = group[speed_bins == label]
-                counts = subset['风向分组'].value_counts().reindex(
-                    wind_directions[:-1], fill_value=0)
-                # 转换为百分比
-                percentage = (counts / counts.sum()) * 100
-
-                # 创建 Barpolar 跟踪,并应用单色渐变
-                trace = go.Barpolar(
-                    r=percentage.values,
-                    theta=counts.index,  # 这里的角度已经适配上北下南左西右东的布局
-                    name=label,
-                    marker_color=colorscale[label],  # 应用颜色尺度
-                    marker_showscale=False,  # 不显示颜色条
-                    marker_line_color='white',  # 设置线条颜色,增加扇区之间的分隔
-                    marker_line_width=1  # 设置线条宽度
-                )
-
-                fig.add_trace(trace)
-
-            # 设置图表的一些基本属性
-            fig.update_layout(
-                title={
-                    "text": f"Wind Rose {name}",
-                    "x": 0.5
-                },
-                polar=dict(
-                    radialaxis=dict(visible=True),
-                    angularaxis=dict(
-                        tickmode="array",
-                        tickvals=wind_directions,
-                        # 明确标注北、东、南、西等方向,以适应以北为0度的布局
-                        ticktext=['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE',
-                                  'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
-
-                        # 更新角度标签,以适应以东为0度的布局
-                        # ticktext=['E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N', 'NNE', 'NE', 'ENE']
-                    )
-                ),
-                legend_title="Wind Speed",
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-            )
-
-            # # 保存html
-            # outputFileHtml = os.path.join(outputAnalysisDir, f"{name}.html")
-            # fig.write_html(outputFileHtml)
-            # 保存图像
-            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            fig.write_image(output_file, scale=2)

+ 0 - 44
dataAnalysisBusiness/algorithm/windSpeedAnalyst.py

@@ -1,44 +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 plotly.express as px  
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-class WindSpeedAnalyst(Analyst):
-
-    def typeAnalyst(self):
-        return "wind_speed"
-
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.drawWindSpeedAnalysis(dataFrameMerge, outputAnalysisDir, confData)
-
-    def drawWindSpeedAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # 检查所需列是否存在
-        required_columns = {Field_NameOfTurbine,confData.field_wind_speed}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-        
-        # 确保'风速'列是数值类型  
-        dataFrameMerge[confData.field_wind_speed] = pd.to_numeric(dataFrameMerge[confData.field_wind_speed], errors='coerce')  
-        
-        # 计算每个turbine_name的平均风速  
-        average_wind_speed = dataFrameMerge.groupby(Field_NameOfTurbine)[confData.field_wind_speed].mean().reset_index()  
-        
-        # 使用plotly绘制柱状图  
-        fig = px.bar(average_wind_speed, x=Field_NameOfTurbine, y=confData.field_wind_speed, title='Turbine Average Wind Speed')  
-        
-        # 更新x轴和y轴的标签  
-        fig.update_xaxes(title_text='Turbine Name', tickangle=-45)  
-        fig.update_yaxes(title_text='Average Wind Speed (m/s)')  
-        # 如果需要进一步调整标题样式或位置(尽管默认情况下标题是居中的)  
-        # 可以使用 update_layout 来设置标题的 x 坐标(xanchor)为 'center' 来确保居中  
-        fig.update_layout(title_x=0.5)  # 设置标题的x位置为图的中心  
-        
-        # 保存图像
-        output_file = os.path.join(outputAnalysisDir, f"WindSpeedAvg_Turbines.png")
-        fig.write_image(output_file)
-

+ 0 - 83
dataAnalysisBusiness/algorithm/windSpeedFrequencyAnalyst.py

@@ -1,83 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import plotly.graph_objects as go
-import plotly.express as px
-from plotly.subplots import make_subplots
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-import matplotlib.pyplot as plt
-from algorithmContract.confBusiness import *
-
-
-class WindSpeedFrequencyAnalyst(Analyst):
-
-    def typeAnalyst(self):
-        return "wind_speed_frequency"
-    
-    def filterCommon(self,dataFrame:pd.DataFrame, confData:ConfBusiness):        
-        return dataFrame
-
-    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.windRoseAnalysis(dataFrameMerge, outputAnalysisDir, confData)
-
-    def windRoseAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # 检查所需列是否存在
-        required_columns = {Field_NameOfTurbine, confData.field_wind_speed}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-
-        wind_speed_bins = np.arange(0, 26, 0.5)  # x轴风速范围 ,间隔0.5
-
-        # 按设备名分组数据
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        for name, group in grouped:
-            # 2. 计算风速频率
-            # 首先,我们需要确定风速的范围并计算每个风速的频数
-            wind_speeds = group[confData.field_wind_speed].unique()
-            # 计算风速频率,确保频率没有零值(用很小的数代替零)  
-            wind_speed_freq = np.histogram(wind_speeds, bins=wind_speed_bins)[0] / len(wind_speeds) * 100  
-
-            # 3. & 4. 确定y轴风速频率的范围和间隔(这里直接计算了频率,所以不需要手动设置间隔)
-            # 我们已经计算了风速频率,因此不需要再手动设置y轴的间隔和范围
-
-            # 5. 使用plotly绘制风速频率分布柱状图
-            # 为了使用plotly绘制柱状图,我们需要将风速范围的中点作为x轴的值
-            x_values = (wind_speed_bins[:-1] + wind_speed_bins[1:]) / 2
-
-            # 创建柱状图
-            fig = px.bar(x=x_values, y=wind_speed_freq)
-
-            # 更新图形的布局
-            fig.update_layout(
-                title={
-                    'text': f'Wind Speed Frequency {name}',
-                    # 'y': 0.95,
-                    'x': 0.5,
-                    'xanchor': 'center',
-                    'yanchor': 'top'
-                },
-                xaxis=dict(
-                    title='Wind Speed (m/s)',
-                    showgrid=True,
-                    range=[0, 26],
-                    dtick=1,
-                    tickangle=-45
-                ),
-                yaxis=dict(
-                    title='Frequency (%)',
-                    showgrid=True,
-                    # range=[0, 1],
-                ),
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-            )
-            # # 更新x轴和y轴的范围和标签
-            # fig.update_yaxes(range=[0, max(wind_speed_freq) * 1.1 if max(wind_speed_freq) > 0 else 0.2], title='Frequency')
-            
-            # # 保存html
-            outputFileHtml = os.path.join(outputAnalysisDir, f"{name}.html")
-            fig.write_html(outputFileHtml)
-            # 保存图像
-            # output_file = os.path.join(outputAnalysisDir, f"{name}.png")
-            # fig.write_image(output_file, scale=2)

+ 0 - 181
dataAnalysisBusiness/algorithm/yawErrorAnalyst.py

@@ -1,181 +0,0 @@
-import os
-import numpy as np
-from plotly.subplots import make_subplots
-import plotly.graph_objects as go
-from scipy.optimize import curve_fit
-import matplotlib.pyplot as plt
-import pandas as pd
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-
-
-class YawErrorAnalyst(Analyst):
-    """
-    风电机组静态偏航误差分析
-    """
-    fieldWindDirFloor = 'wind_dir_floor'
-    fieldPower = 'power'
-    fieldPowerMean = 'mean_power'
-    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'
-
-    def typeAnalyst(self):
-        return "yaw_error"
-
-    def filterCommon(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
-        dataFrame = super().filterCommon(dataFrame, confData)
-
-        dataFrame = dataFrame[~((dataFrame[Field_AngleIncluded].abs() >= 90))]
-
-        return dataFrame
-
-    def calculateYawError(self, dataFrame: pd.DataFrame, fieldAngleInclude, fieldActivePower):
-        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
-
-    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, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        self.yawErrorAnalysis(dataFrameMerge, outputAnalysisDir, confData)
-
-    def yawErrorAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
-        # 检查所需列是否存在
-        required_columns = {confData.field_power,confData.field_angle_included}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-        
-        results = []
-        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
-
-        for name, group in grouped:
-            df = self.calculateYawError(
-                group, confData.field_angle_included, confData.field_power)
-
-            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
-            )
-
-            # Update layout
-            fig.update_layout(
-                title={
-                    "text": f"Yaw Error Analysis {name}",
-                    "x": 0.5
-                },
-                showlegend=True,
-                margin=dict(t=50, b=10)  # t为顶部(top)间距,b为底部(bottom)间距
-            )
-
-            # Save to file
-            fig.write_image(os.path.join(outputAnalysisDir, f"{name}.png"))
-
-            # calc squear error of fitted_ydata and ydata
-            print(name, "\t", df[self.fieldWindDirFloor][max_pos])
-
-            resultOfTurbine = [name, df[self.fieldWindDirFloor][max_pos]]
-            results.append(resultOfTurbine)
-
-        # 初始化一个空的DataFrame,指定列名
-        columns = [Field_NameOfTurbine, Field_YawError]
-        dataFrameResult = pd.DataFrame(results, columns=columns)
-
-        filePathOfYawError = os.path.join(
-            outputAnalysisDir, f"yaw_error_result{CSVSuffix}")
-        dataFrameResult.to_csv(filePathOfYawError, index=False)

+ 0 - 0
appService/service/__init__.py → dataAnalysisBusiness/common/__init__.py


+ 0 - 0
dataAnalysisBehavior/behavior/__init__.py → dataAnalysisBusiness/dataContract/__init__.py


+ 148 - 0
dataAnalysisBusiness/dataContract/confBusiness.py

@@ -0,0 +1,148 @@
+import pandas as pd
+from algorithm.utils.jsonUtil.jsonUtil import JsonUtil
+
+# 全局变量
+charset_unify = 'utf-8'
+
+Field_NameOfTurbine = "turbine_name"
+Field_GeneratorTorque = "generator_torque"
+Field_AngleIncluded="angle_included"
+
+
+class ConfBusiness:
+    def __init__(self):
+        self.farm_name = None
+        self.rated_power = None
+        self.rated_WindSpeed = None
+        self.rotor_diameter = None
+        self.density_air = None
+        self.rotational_Speed_Ratio = None
+
+        self.type_name = None
+
+        self.time_period = None            # 时间间隔,单位是秒
+
+        self.output_name = None
+        self.output_prefix = None
+
+        self.turbineInfoFilePathCSV = None  # 风电机组信息
+        self.turbineGuaranteedPowerCurveFilePathCSV = None  # 合同担保功率曲线
+
+        self.input_path = None
+        self.skip_row_number = None  # 跳过的行数
+        self.csvFileNameSplitStringForTurbine = None  # 自文件名中获取机组号的分隔符
+        self.index_turbine = None  # 自文件名中获取机组号的索引
+        self.filter = None
+
+        self.output_path = None
+
+        self.start_time_str = None
+        self.end_time_str = None
+
+        # 将字符串转换为 pd.Timestamp 类型
+        self.start_time = None
+        self.end_time = None
+        self.excludingMonths=None # 排除指定的月份数据 格式%Y-%m
+
+        self.field_turbine_time = None    # 字段名 时间
+        self.field_turbine_name = None    # 字段名 机组名
+
+        self.field_wind_speed = None      # 字段名 风速
+        self.field_power = None           # 字段名 有功功率
+        self.field_pitch_angle1 = None    # 字段名 桨距角1
+        self.field_pitch_angle2 = None    # 字段名 桨距角2
+        self.field_pitch_angle3 = None    # 字段名 桨距角3
+        self.field_turbine_state = None   # 字段名 风机状态
+        self.field_gen_speed = None       # 字段名 发电机转速
+        self.value_gen_speed_min = None       # 值 发电机转速最小
+        self.value_gen_speed_max = None       # 值 发电机转速最大
+        self.field_rotor_speed = None     # 字段名 叶轮转速
+        self.field_torque = None          # 字段名 转矩
+        self.field_wind_dir = None        # 字段名 风向
+        self.field_angle_included = None
+        self.field_nacelle_pos = None     # 字段名 机舱温度
+        self.field_env_temp = None        # 字段名 环境温度
+        self.field_nacelle_temp = None    # 字段名 机舱温度
+        self.field_temperature_large_components = None  # 字段名列表  大部件温度传感器
+
+    def loadConfig(self,jsonFilePath, charset=charset_unify):
+        """
+        配置初始化
+        """
+        # # 使用global声明,表示我们要修改的是全局变量config_data
+        # global farm_name
+
+        # 将配置数据存储在变量中
+        configData = JsonUtil.read_json(jsonFilePath)
+                
+        self.farm_name = configData['name_PowerFarm']
+        self.rated_power = configData['rated_Power_Turbine_Unit_kW']
+        self.rated_WindSpeed = configData["rated_WindSpeed"]
+        self.rotor_diameter = configData['rotor_diameter']
+        self.rotational_Speed_Ratio = configData['rotational_Speed_Ratio']
+        self.density_air = configData['density_air']
+
+        self.type_name = configData['name_Type_For_Analysis']
+        # 时间间隔,单位是秒
+        self.time_period = configData['time_Period_Unit_Second']
+
+        self.output_name = configData['name_Output']
+        self.output_prefix = configData['outputFileDirectory']
+
+        self.turbineInfoFilePathCSV = configData["turbineInfoFilePathCSV"]
+        self.turbineGuaranteedPowerCurveFilePathCSV = configData[
+            "turbineGuaranteedPowerCurveFilePathCSV"]
+        self.input_path = configData['inputFileDirectoryByCSV']
+        self.csvFileNameSplitStringForTurbine = configData["csvFileNameSplitStringForTurbine"]
+        self.index_turbine = configData["index_turbine"]
+        self.skip_row_number = configData['skip_row_number']
+        self.filter = configData['filter']
+
+        self.output_path = self.output_prefix + \
+            r"/{}".format(self.farm_name)
+
+        # start_time_str = '{} 00:00:00'.format(configData['date_Begin'])
+        # end_time_str = '{} 23:59:59'.format(configData['date_End'])
+        self.start_time_str = configData['date_Begin']
+        self.end_time_str = configData['date_End']
+
+        # 将字符串转换为 pd.Timestamp 类型
+        self.start_time = pd.to_datetime(
+            self.start_time_str, format='%Y-%m-%d %H:%M:%S')
+        self.end_time = pd.to_datetime(
+            self.end_time_str, format='%Y-%m-%d %H:%M:%S')
+        self.excludingMonths= configData['excludingMonths']
+
+        self.field_turbine_time = configData['turbine_Time']
+        self.field_turbine_name = configData['turbine_Name']
+
+        self.field_wind_speed = configData['speed_Wind']
+        self.field_power = configData['power_Active']
+        self.field_pitch_angle1 = configData['pitch_Angle1']
+        self.field_pitch_angle2 = configData['pitch_Angle2']
+        self.field_pitch_angle3 = configData['pitch_Angle3']
+        self.field_turbine_state = configData['state_Turbine']
+        self.field_gen_speed = configData['speed_Generator']
+        self.value_gen_speed_min = configData['speed_Generato_min']
+        self.value_gen_speed_max = configData['speed_Generato_max']
+        self.field_rotor_speed = configData['speed_Rotor']
+        self.field_torque = configData['torque']
+        self.field_wind_dir = configData['direction_Wind']
+        self.field_angle_included = configData['angle_included']
+        self.field_nacelle_pos = configData['nacelle_Pos']
+        self.field_env_temp = configData['temperature_Env']
+        self.field_nacelle_temp = configData['temperature_Nacelle']
+        self.field_temperature_large_components = configData['temperature_large_components']
+
+        return self
+        
+    # def add_W_if_starts_with_digit(self,s):  
+    #     if s and s[0].isdigit():  
+    #         return 'W' + s  
+    #     return s  
+    
+    # 定义一个函数,用于检查字符串首字母是否为数字,并在是的情况下添加'W'  
+    def add_W_if_starts_with_digit(self,s):  
+        if isinstance(s, str) and s[0].isdigit():  
+            return 'W' + s  
+        return s  

+ 0 - 450
dataAnalysisBusiness/demo/SCADA_10min_category_0.py

@@ -1,450 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Created on Mon Apr  8 15:01:43 2024
-
-@author: LDDN
-"""
-import math
-import pandas as pd  
-import numpy as np
-import matplotlib.pyplot as plt
-from matplotlib.pyplot import MultipleLocator#设定固定刻度
-
-
-scada_10min = pd.read_csv(r'E:\BaiduNetdiskDownload\test\min_scada_LuoTuoGou\72\82.csv',encoding="utf-8")  #.value是将单元格
-turbine_info = pd.read_csv(r'E:\BaiduNetdiskDownload\test\min_scada_LuoTuoGou\72\info.csv')  #.value是将单元格
-PRated = turbine_info.loc[:,["额定功率"]] #2000
-PRated = PRated.values
-VCutOut = turbine_info.loc[:,["切出风速"]]  #25
-VCutOut = VCutOut.values
-VCutIn = turbine_info.loc[:,["切入风速"]]  #3
-VCutIn = VCutIn.values
-VRated = turbine_info.loc[:,["额定风速"]] #10
-VRated = VRated.values
-
-time_stamp = scada_10min.loc[:,['时间']] #dataframe
-active_power = scada_10min.loc[:,['变频器电网侧有功功率']]
-wind_speed = scada_10min.loc[:,['风速']]
-LM = pd.concat([time_stamp,active_power,wind_speed],axis=1)  #dataframe
-
-
-Labeled_March809 = LM
-APower = Labeled_March809["变频器电网侧有功功率"]  #series读入有功功率
-WSpeed = Labeled_March809["风速"]  #读入风速
-maxP=np.max(APower)
-intervalP=25  #ceil(PRated*0.01)#功率分区间隔为额定功率的1%
-intervalwindspeed=0.25  #风速分区间隔0.25m/s
-
-#初始化
-PNum = 0  
-TopP = 0   
-# 根据条件计算PNum和TopP  
-if maxP >= PRated:  
-    PNum = math.floor(maxP / intervalP) + 1  
-    TopP = math.floor((maxP - PRated) / intervalP) + 1  
-else:  
-    PNum = math.floor(PRated / intervalP)  
-    TopP = 0   
-VNum = math.ceil(VCutOut / intervalwindspeed)  
-  
-SM1 = Labeled_March809.shape
-AA1 = SM1[0]  
-lab = [[0] for _ in range(AA1)]
-lab = pd.DataFrame(lab,columns=['lab'])
-Labeled_March809 = pd.concat([Labeled_March809,lab],axis=1)  #在tpv后加一列标签列
-Labeled_March809 = Labeled_March809.values
-SM = Labeled_March809.shape #(52561,4)
-AA = SM[0]  
-#存储功率大于0的运行数据
-#标识功率为0的点,标识-1
-DzMarch809_0 = np.zeros((AA, 3)) # 初始化数组来存储功率大于零的运行数据  
-nCounter1 = 0 
-Point_line = np.zeros(AA, dtype=int)  
-#考虑到很多功率小于10的数据存在,将<10的功率视为0
-for i in range(AA):
-    if (APower[i] > 10) & (WSpeed[i] > 0):
-        nCounter1 += 1   #共有nCounter1个功率大于0的正常数据
-        DzMarch809_0[nCounter1-1, 0] = WSpeed[i]  
-        DzMarch809_0[nCounter1-1, 1] = APower[i]  
-        Point_line[nCounter1-1] = i+1  # 记录nCounter1记下的数据在原始数据中的位置  
-    if APower[i] <= 10: 
-        Labeled_March809[i,SM[1]-1] = -1  # 功率为0标识为-1  array类型
-# 截取DzMarch809_0中实际存储的数据  其他全为0
-DzMarch809 = DzMarch809_0[:nCounter1, :]  
-#统计各网格落入的散点个数
-XBoxNumber = np.ones((PNum, VNum),dtype=int)  #(86 100)
-nWhichP = 0
-nWhichV = 0
-
-# 循环遍历DzMarch809中的有效数据  
-for i in range(nCounter1):  
-    
-    # 查找功率所在的区间  
-    for m in range(1, PNum + 1):  # 注意Python的range是左闭右开的,所以需要+1  
-        if (DzMarch809[i,1] > (m - 1) * intervalP) and (DzMarch809[i,1] <= m * intervalP):  
-            nWhichP = m  
-            break  
-      
-    # 查找风速所在的区间  
-    for n in range(1, VNum + 1):  # 同样需要+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  # 注意Python的索引是从0开始的,所以需要减1  
-# XBoxNumber现在包含了每个网格的计数[PNum行, VNum列]
-
-for m in range(1,PNum+1):
-    for n in range(1,VNum+1):
-        XBoxNumber[m-1,n-1] = XBoxNumber[m-1,n-1] - 1
-
-#在功率方向将网格内散点绝对个数转换为相对百分比,备用
-PBoxPercent = np.zeros((PNum, VNum),dtype = float)  #(86 100) #计算后会出现浮点型,所以不能定义int类型
-PBinSum = np.zeros((PNum,1),dtype=int)
-for i in range(1,PNum+1):
-    for m in range(1,VNum+1):
-        PBinSum[i-1] = PBinSum[i-1] + XBoxNumber[i-1,m-1] 
-    for m in range(1,VNum+1):
-        if PBinSum[i-1]>0:
-            PBoxPercent[i-1,m-1] = (XBoxNumber[i-1,m-1] / PBinSum[i-1])*100
-#在风速方向将网格内散点绝对个数转换为相对百分比,备用          
-VBoxPercent = np.zeros((PNum, VNum))  #(86 100) #计算后会出现浮点型,所以不能定义int类型
-VBinSum = np.zeros((VNum,1),dtype=int)
-for i in range(1,VNum+1):
-    for m in range(1,PNum+1):
-        VBinSum[i-1] = VBinSum[i-1] + XBoxNumber[m-1,i-1] 
-    for m in range(1,PNum+1):
-        if VBinSum[i-1]>0:
-            VBoxPercent[m-1,i-1] = (XBoxNumber[m-1,i-1] / VBinSum[i-1])*100
-# VBoxPercent PBoxPercent 左上-右下
-# 将数据颠倒一下  左下-右上         第一行换为倒数第一行 方便可视化
-InvXBoxNumber = np.zeros((PNum,VNum),dtype = int)
-InvPBoxPercent = np.zeros((PNum,VNum),dtype = float)
-InvVBoxPercent = np.zeros((PNum,VNum),dtype = float)
-for m in range(1,PNum+1):
-    for n in range(1,VNum+1):
-        InvXBoxNumber[m-1,n-1] = XBoxNumber[PNum-(m-1)-1,n-1]
-        InvPBoxPercent[m-1,n-1] = PBoxPercent[PNum-(m-1)-1,n-1]
-        InvVBoxPercent[m-1,n-1] = VBoxPercent[PNum-(m-1)-1,n-1]
-
-#以水平功率带方向为准,分析每个水平功率带中,功率主带中心,即找百分比最大的网格位置。
-PBoxMaxIndex = np.zeros((PNum,1),dtype = int)  #水平功率带最大网格位置索引
-PBoxMaxP = np.zeros((PNum,1),dtype = float)       #水平功率带最大网格百分比
-for m in range(1,PNum+1):
-    PBoxMaxIndex[m-1] = np.argmax(PBoxPercent[m-1, :])   #argmax返回最大值的索引
-    PBoxMaxP[m-1] = np.max(PBoxPercent[m-1, :])
-#以垂直风速方向为准,分析每个垂直风速带中,功率主带中心,即找百分比最大的网格位置。
-VBoxMaxIndex = np.zeros((VNum,1),dtype = int)  
-VBoxMaxV = np.zeros((VNum,1),dtype = float)       
-for m in range(1,VNum+1):
-    VBoxMaxIndex[m-1] = np.argmax(VBoxPercent[:, m-1])   
-    VBoxMaxV[m-1] = np.max(VBoxPercent[:, m-1])
-
-#切入风速特殊处理,如果切入风速过于偏右,向左拉回
-if PBoxMaxIndex[0]>14:                     #第一个值对应的是风速最小处 即切入风速
-    PBoxMaxIndex[0] = 9 
-#以水平功率带方向为基准,进行分析
-DotDense = np.zeros(PNum)   #每一水平功率带的功率主带包含的网格数
-DotDenseLeftRight = np.zeros((PNum,2))  #存储每一水平功率带的功率主带以最大网格为中心,向左,向右扩展的网格数
-DotValve = 90  #从中心向左右对称扩展网格的散点百分比和的阈值。
-PDotDenseSum = 0
-for i in range(PNum - TopP):  # 从最下层水平功率带开始,向上分析到特定的功率带  
-    PDotDenseSum = PBoxMaxP[i]  # 以中心最大水平功率带为基准,向左向右对称扩展网格,累加各网格散点百分比  
-    iSpreadRight = 1  
-    iSpreadLeft = 1  
-      
-    while PDotDenseSum < DotValve:  
-        if (PBoxMaxIndex[i] + iSpreadRight) < VNum-1-1:  
-            PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[i] + iSpreadRight]  # 向右侧扩展  
-            iSpreadRight += 1  
-        else:
-            break  
-          
-        if (PBoxMaxIndex[i] - iSpreadLeft) > 0:  
-            PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[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  # 记录向左向右扩展的个数及每个功率仓内网格的个数  
-# 此时DotDense和DotDenseLeftRight数组已经包含了所需信息    
-#各行功率主带右侧宽度的中位数最具有代表性(因为先右后左)
-DotDenseWidthLeft = np.zeros((PNum-TopP))
-for i in range(PNum-TopP):
-    DotDenseWidthLeft[i] = DotDenseLeftRight[i,1]  #DotDenseLeftRight[i,1]:向右延伸个数
-MainBandRight = np.median(DotDenseWidthLeft) #计算中位数
-
-# 初始化变量  
-PowerLimit = np.zeros(PNum, dtype=int)  # 各水平功率带是否为限功率标识,1:是;0:不是  
-WidthAverage = 0  # 功率主带右侧平均宽度  
-WidthAverage_L = 0  # 功率主带左侧平均宽度  
-WidthVar = 0  # 功率主带方差(此变量在提供的代码中并未使用)  
-PowerLimitValve = 6  # 限功率主带判别阈值  
-N_Pcount = 20  # 阈值  
-  
-nCounterLimit = 0  # 限功率的个数  
-nCounter = 0  # 正常水平功率带的个数  
-  
-# 循环遍历水平功率带,从第1个到第PNum-TopP个  
-for i in range(PNum - TopP):  
-    # 如果向右扩展网格数大于阈值,且该水平功率带点总数大于20,则标记为限功率带  
-    if (DotDenseLeftRight[i, 1] > PowerLimitValve) and (PBinSum[i] > N_Pcount):  
-        PowerLimit[i] = 1  
-        nCounterLimit += 1  #限功率的个数
-      
-    # 如果向右扩展网格数小于等于阈值,则累加右侧宽度(左侧宽度在代码中似乎有误)  
-    if DotDenseLeftRight[i, 1] <= PowerLimitValve:  
-        WidthAverage += DotDenseLeftRight[i, 1]  # 统计正常水平功率带右侧宽度
-        WidthAverage_L += DotDenseLeftRight[i,1]   #统计正常水平功率带左侧宽度
-        nCounter += 1  
-# 计算平均宽度  
-WidthAverage /= nCounter if nCounter > 0 else 1  # 避免除以0的情况  
-WidthAverage_L /= nCounter if nCounter > 0 else 1   
-
-#计算正常(即非限功率)水平功率带的功率主带宽度的方差,以此来反映从下到上宽度是否一致
-WidthVar = 0  # 功率主带宽度的方差   
-for i in range(PNum - TopP):  
-    # 如果向右扩展网格数小于等于阈值,则计算当前宽度与平均宽度的差值平方  
-    if DotDenseLeftRight[i, 1] <= PowerLimitValve:  
-        WidthVar += (DotDenseLeftRight[i, 1] - WidthAverage) ** 2  
-# 计算方差(注意:除以nCounter-1是为了得到样本方差)  
-WidthVar = np.sqrt(WidthVar / (nCounter - 1) if nCounter > 1 else 0)  # 避免除以0的情况
-
-#各水平功率带,功率主带的风速范围,右侧扩展网格数*2*0.25
-PowerBandWidth = WidthAverage*intervalwindspeed+WidthAverage_L*intervalwindspeed
-
-# 对限负荷水平功率带的最大网格进行修正  
-for i in range(1, PNum - TopP+1):  
-    if (PowerLimit[i] == 1) and (abs(PBoxMaxIndex[i] - PBoxMaxIndex[i - 1]) > 5):  
-        PBoxMaxIndex[i] = PBoxMaxIndex[i - 1] + 1  
-  
-# 输出各层功率主带的左右边界网格索引  
-DotDenseInverse = np.flipud(DotDenseLeftRight)  # 上下翻转数组以得到反向顺序  
-  
-# 计算功率主带的左右边界  
-CurveWidthR = np.ceil(WidthAverage) + 2  # 功率主带的右边界 + 2  
-CurveWidthL = np.ceil(WidthAverage_L) + 2  # 功率主带的左边界 + 2  
-  
-# 网格是否为限功率网格的标识数组  
-BBoxLimit = np.zeros((PNum, VNum), dtype=int)  
-# 标记限功率网格  
-for i in range(2, PNum - TopP):  
-    if PowerLimit[i] == 1:
-        BBoxLimit[i, int(PBoxMaxIndex[i] + CurveWidthR + 1):VNum] = 1
-
-# 初始化数据异常需要剔除的网格标识数组  
-BBoxRemove = np.zeros((PNum, VNum), dtype=int)  
-# 标记需要剔除的网格  
-for m in range(PNum - TopP): 
-    for n in range(int(PBoxMaxIndex[m]) + int(CurveWidthR), VNum):  # 注意Python中的索引从0开始,因此需要减去1  
-        BBoxRemove[m, n] = 1 
-    # 功率主带左侧的超发网格,从最大索引向左直到第一个网格  
-    
-    for n in range(int(PBoxMaxIndex[m]) - int(CurveWidthL)+1, 0, -1):  # 使用range的步长参数来实现从右向左的迭代  
-        BBoxRemove[m, n-1] = 2  # 注意Python中的索引从0开始,因此需要减去1
-
-# 初始化变量  
-CurveTop = np.zeros((2, 1), dtype=int)  
-CurveTopValve = 1  # 网格的百分比阈值  
-BTopFind = 0  
-mm = 0  
-#确定功率主带的左上拐点,即额定风速位置的网格索引
-CurveTop = np.zeros((2, 1), dtype=int)  
-CurveTopValve = 1  # 网格的百分比阈值  
-BTopFind = 0  
-mm = 0   
-for m in range(PNum - TopP, 0, -1):  # 注意Python的range是左闭右开区间,所以这里从PNum-TopP开始到1(不包括0)  
-    for n in range(int(np.floor(int(VCutIn) / intervalwindspeed)), VNum - 1):  # 使用floor函数来向下取整  
-        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 = m  # mm是拐点所在功率仓,对应其index
-            break  # 找到后退出内层循环  
-    if BTopFind == 1:  
-        break  # 找到后退出外层循环
-        
-IsolateValve = 3  #功率主带右侧孤立点占比功率仓阈值 3%
-# 遍历功率仓和网格  
-for m in range(PNum - TopP):    
-    for n in range(int(PBoxMaxIndex[m]) + int(CurveWidthR), VNum):  
-        # 检查PBoxPercent是否小于阈值,如果是,则标记BBoxRemove为1  
-        if PBoxPercent[m, n] < IsolateValve:   
-            BBoxRemove[m, n] = 1
-#功率主带顶部宽度
-CurveWidthT = np.floor((maxP - PRated) / intervalP) + 1  
-# 标记额定功率以上的超发点(PNum-PTop之间)  
-for m in range(PNum - TopP, PNum):   
-    for n in range(VNum):  
-        BBoxRemove[m, n] = 3
-  
-# 标记功率主带拐点左侧的欠发网格  
-for m in range(mm-1, PNum - TopP): 
-    for n in range(int(CurveTop[1]) - 2):
-        BBoxRemove[m, n] = 2    # BBoxRemove数组现在包含了根据条件标记的超发点和欠发网格的信息
-
-#以网格的标识,决定该网格内数据的标识。
-# DzMarch809Sel数组现在包含了每个数据点的标识
-DzMarch809Sel = np.zeros(nCounter1, dtype=int)  # 初始化标识数组   
-nWhichP = 0  
-nWhichV = 0  
-nBadA = 0   
-for i in range(nCounter1):  
-    for m in range( PNum ):   
-        if (DzMarch809[i, 1] > m * intervalP) and (DzMarch809[i, 1] <= (m+1) * intervalP):  
-            nWhichP = m  #m记录的是index
-            break  
-    for n in range( VNum ):  # 注意Python的range是左闭右开区间,所以这里到VNum+1  
-        if DzMarch809[i, 0] > ((n+1) * intervalwindspeed - intervalwindspeed/2) and DzMarch809[i, 0] <= ((n+1) * intervalwindspeed + intervalwindspeed / 2):  
-            nWhichV = n  #index
-            break  
-    if nWhichP >= 0 and nWhichV >= 0:  
-        if BBoxRemove[nWhichP, nWhichV] == 1:   
-            DzMarch809Sel[i] = 1  
-            nBadA += 1  
-        elif BBoxRemove[nWhichP, nWhichV] == 2:  
-            DzMarch809Sel[i] = 2  
-        elif BBoxRemove[nWhichP , nWhichV] == 3:  
-            DzMarch809Sel[i] = 0  # 额定风速以上的超发功率点认为是正常点,不再标识  
-# DzMarch809Sel数组现在包含了每个数据点的标识
-
-##############################滑动窗口方法
-# 存储限负荷数据  
-PVLimit = np.zeros((nCounter1, 3))  #存储限负荷数据  %第3列用于存储限电的点所在的行数
-nLimitTotal = 0  
-nWindowLength = 6   #滑动窗口长度设置为6
-LimitWindow = np.zeros(nWindowLength)  #滑动窗口空列表
-UpLimit = 0    #上限
-LowLimit = 0   #下限
-PowerStd = 30  # 功率波动方差  
-nWindowNum = np.floor(nCounter1/nWindowLength) #6587
-PowerLimitUp = PRated - 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  
-# PVLimit现在包含了限负荷数据,nLimitTotal是限负荷数据的总数
-
-
-#将功率滑动窗口主带平滑化
-# 初始化锯齿平滑的计数器  
-nSmooth = 0  
-# 遍历除了最后 TopP+1 个元素之外的所有 PBoxMaxIndex 元素  
-for i in range(PNum - TopP - 1):  
-    PVLeftDown = np.zeros(2)  
-    PVRightUp = np.zeros(2)  
-    # 检查当前与下一个 PBoxMaxIndex 之间的距离是否大于等于1  
-    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  
-          
-        # 遍历 DzMarch809 数组  
-        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 是选中的点数
-
-
-###################################存储数据
-# 存储好点  
-nCounterPV = 0  # 初始化计数器  
-PVDot = np.zeros((nCounter1, 3))  # 初始化存储好点的数组  nCounter1是p>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]  # 好点 Point_line记录nCounter1在原始数据中的位置 
-nCounterVP = nCounterPV  
- 
-# 对所有数据中的好点进行标注    
-for i in range(nCounterVP):  
-    Labeled_March809[int(PVDot[i, 2] - 1), (SM[1]-1)] = 1  # 注意Python的索引从0开始,并且需要转换为整数索引  
- 
-# 存储坏点  
-nCounterBad = 0  # 初始化计数器  
-PVBad = np.zeros((nCounter1, 3))  # 初始化存储坏点的数组  
-for i in range(nCounter1):  
-    if DzMarch809Sel[i] in [1, 2, 3]:  
-        nCounterBad += 1  
-        PVBad[nCounterBad-1, :2] = DzMarch809[i, :2]  
-        PVBad[nCounterBad-1, 2] = Point_line[i]  
-    
-# 对所有数据中的坏点进行标注  
-for i in range(nCounterBad):  
-    Labeled_March809[int(PVBad[i, 2] - 1),(SM[1]-1)] = 5  # 坏点标识  
-
-# 对所有数据中的限电点进行标注   
-for i in range(nLimitTotal):  
-    Labeled_March809[int(PVLimit[i, 2] - 1),(SM[1]-1)] = 4  # 限电点标识  
-# 对所有的数据点进行标注  
-# Labeled_March809是array,提取所第四列的值保存为dataframe
-A = Labeled_March809[:,3]
-A=pd.DataFrame(A,columns=['lab'])
-
-
-mergedTable = pd.concat([scada_10min,A],axis=1)#合并dataframe
-D = mergedTable[mergedTable['lab'] == 1]#选择为1的行
-
-ws = D["风速"].values  #array
-ap = D["变频器电网侧有功功率"]
-
-# fig=plt.figure(figsize=(10,6),dpi=500)  #figsize是图形大小,dpi像素
-fig=plt.figure()  #figsize是图形大小,dpi像素
-plt.scatter(ws,ap,s=1,c='black',marker='.') #'.'比'o'要更小
-
-# plt.scatter(x2,y2,s=10,c='b',marker='.',label='5.8-6.5建模噪声点')
-
-x_major_locator=MultipleLocator(5)
-y_major_locator=MultipleLocator(500)
-ax=plt.gca()
-ax.xaxis.set_major_locator(x_major_locator)
-ax.yaxis.set_major_locator(y_major_locator)
-plt.xlim((0,30))
-plt.ylim((0,2200))
-plt.tick_params(labelsize=8)
-
-# plt.grid(c='dimgray',alpha=0.2)
-
-plt.xlabel("V/(m$·$s$^{-1}$)",fontsize=8)
-plt.ylabel("P/kW",fontsize=8)
-
-# plt.savefig(r'D:\赵雅丽\研究生学习资料\学习资料\劣化度健康度\spyder\大论文\图\风速-功率.jpg',bbox_inches='tight')
-plt.show()

+ 0 - 507
dataAnalysisBusiness/demo/SCADA_10min_category_1.py

@@ -1,507 +0,0 @@
-import os
-import re
-import math
-import pandas as pd
-import numpy as np
-import matplotlib.pyplot as plt
-from matplotlib.pyplot import MultipleLocator#设定固定刻度
-
-def scada_10min_category():
-    turbine_number=24
-    
-    fpath = 'D:/赵雅丽/实习/算法/min_scada_LuoTuoGou/72/'
-    # 定义一个正则表达式来匹配纯数字文件名且扩展名为.csv  
-    pattern = re.compile(r'^\d+\.csv$')
-    
-    # 列出指定路径下的所有文件和文件夹  
-    files_in_dir = os.listdir(fpath)
-    for file in files_in_dir:  
-        # 使用正则表达式匹配文件名  
-        if pattern.match(file):  
-            # 拼接文件的完整路径  
-            fname = os.path.join(fpath, file)
-            # 读取csv文件,保持原始变量名而不忽略任何行
-            scada_10min = pd.read_csv(fname)
-        
-            # 显示数据        
-            time_stamp = scada_10min.loc[:,['时间']] #dataframe
-            active_power = scada_10min.loc[:,['变频器电网侧有功功率']]
-            wind_speed = scada_10min.loc[:,['风速']]
-            LM = pd.concat([time_stamp,active_power,wind_speed],axis=1)  #dataframe
-            # lm=LM.values #array
-    
-            xx = data_label(LM,fpath)#dataframe
-            mergedTable = pd.concat([scada_10min,xx],axis=1)#合并dataframe
-            D = mergedTable[mergedTable['lab'] == 1]#选择为1的行
-            ws = D["风速"]#series
-            ap = D["变频器电网侧有功功率"]
-            ##绘图
-            # fig = plt.figure(figsize=(10,6),dpi=500)  #figsize是图形大小,dpi像素
-            plt.scatter(ws,ap,s=8,c='black',marker='.',label='好点')
-            # x_major_locator=MultipleLocator(5)
-            # y_major_locator=MultipleLocator(500)
-            # ax=plt.gca()
-            # ax.xaxis.set_major_locator(x_major_locator)
-            # ax.yaxis.set_major_locator(y_major_locator)
-            # plt.xlim((0,30))
-            # plt.ylim((0,2200))
-            # plt.tick_params(labelsize=20)
-            # # plt.grid(c='dimgray',alpha=0.2)
-            # plt.xlabel("V/(m$·$s$^{-1}$)",fontsize=20)
-            # plt.ylabel("P/kW",fontsize=20)
-
-            # # plt.savefig(r'D:\赵雅丽\研究生学习资料\学习资料\劣化度健康度\spyder\大论文\图\风速-功率.jpg',bbox_inches='tight')
-            # plt.show()
-        
-        
-        
-def data_label(x1,x2):   # LM:T P V  path:文件获取路径
-    fpath2 = x2
-    fname2 = os.path.join(fpath2, "info.csv") #读取数据文件2(额定风速额定功率等)
-    # 参数na_filter=False仅阻止了pandas自动检测这些缺失值,并不能忽略  
-    # 但请注意,pandas没有直接的'omitrow'选项,如果需要忽略包含缺失值的行,需要在后续处理中处理
-    turbine_info = pd.read_csv(fname2, na_filter=False)
-    # 删除包含任何缺失值的行  
-    turbine_info = turbine_info.dropna() 
-    
-    PRated = turbine_info.loc[:,["额定功率"]] #dataframe
-    VCutOut = turbine_info.loc[:,["切出风速"]]  
-    VCutIn = turbine_info.loc[:,["切入风速"]]  
-    VRated = turbine_info.loc[:,["额定风速"]]
-    
-    #网格法确定风速风向分区数量,功率方向分区数量
-    Labeled_March809 = x1
-    APower = Labeled_March809["active_power"]  #series读入有功功率
-    WSpeed = Labeled_March809["wind_speed"]  #读入风速
-    maxP=np.max(APower)
-    intervalP=25  #ceil(PRated*0.01)#功率分区间隔为额定功率的1%
-    intervalwindspeed=0.25  #风速分区间隔0.25m/s
-    #初始化
-    PNum = 0  
-    TopP = 0   
-    # 根据条件计算PNum和TopP  
-    if maxP >= PRated:  
-        PNum = math.floor(maxP / intervalP) + 1  
-        TopP = math.floor((maxP - PRated) / intervalP) + 1  
-    else:  
-        PNum = math.floor(PRated / intervalP)  
-        TopP = 0   
-    VNum = math.ceil(VCutOut / intervalwindspeed)  
-    SM1 = Labeled_March809.shape  
-    AA1 = SM1[0]  #运行数据的条数
-    lab = [[0] for _ in range(AA1)]  #创建全0空列表
-    lab = pd.DataFrame(lab,columns=['lab'])
-    Labeled_March809 = pd.concat([Labeled_March809,lab],axis=1)  #在tpv后加一列标签列
-    SM = Labeled_March809.shape #(52561,4)
-    AA = SM[0]  
-    #存储功率大于0的运行数据
-    #标识功率为0的点,标识-1
-    DzMarch809_0 = np.zeros(AA, 3)  #array(52561,3)
-    nCounter1 = 1
-    Point_line=np.zeros(AA,1)
-    #考虑到很多功率小于10的数据存在,将<10的功率视为0
-    for i in range(AA):
-        if (APower[i] > 10) & (WSpeed[i] > 0):
-            nCounter1 += 1   #共有nCounter1个功率大于0的正常数据
-            DzMarch809_0[nCounter1-1, 0] = WSpeed[i]  
-            DzMarch809_0[nCounter1-1, 1] = APower[i]  
-            Point_line[nCounter1-1] = i+1  # 记录nCounter1记下的数据在原始数据中的位置  
-        if APower[i] <= 10: 
-            Labeled_March809[i,SM[1]-1] = -1  # 功率为0标识为-1  array类型
-    # 截取DzMarch809_0中实际存储的数据  其他全为0
-    DzMarch809 = DzMarch809_0[:nCounter1, :]  
-    #统计各网格落入的散点个数
-    XBoxNumber = np.ones((PNum, VNum),dtype=int)  #(86 100)
-    nWhichP = 0
-    nWhichV = 0
-
-    # 循环遍历DzMarch809中的有效数据  
-    for i in range(nCounter1):  
-        
-        # 查找功率所在的区间  
-        for m in range(1, PNum + 1):  # 注意Python的range是左闭右开的,所以需要+1  
-            if (DzMarch809[i,1] > (m - 1) * intervalP) and (DzMarch809[i,1] <= m * intervalP):  
-                nWhichP = m  
-                break  
-          
-        # 查找风速所在的区间  
-        for n in range(1, VNum + 1):  # 同样需要+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  # 注意Python的索引是从0开始的,所以需要减1  
-    # XBoxNumber现在包含了每个网格的计数[PNum行, VNum列]
-
-    for m in range(1,PNum+1):
-        for n in range(1,VNum+1):
-            XBoxNumber[m-1,n-1] = XBoxNumber[m-1,n-1] - 1
-
-    #在功率方向将网格内散点绝对个数转换为相对百分比,备用
-    PBoxPercent = np.zeros((PNum, VNum))  #(86 100) #计算后会出现浮点型,所以不能定义int类型
-    PBinSum = np.zeros((PNum,1),dtype=int)
-    for i in range(1,PNum+1):
-        for m in range(1,VNum+1):
-            PBinSum[i-1] = PBinSum[i-1] + XBoxNumber[i-1,m-1] 
-        for m in range(1,VNum+1):
-            if PBinSum[i-1]>0:
-                PBoxPercent[i-1,m-1] = (XBoxNumber[i-1,m-1] / PBinSum[i-1])*100
-    #在风速方向将网格内散点绝对个数转换为相对百分比,备用          
-    VBoxPercent = np.zeros((PNum, VNum))  #(86 100) #计算后会出现浮点型,所以不能定义int类型
-    VBinSum = np.zeros((VNum,1),dtype=int)
-    for i in range(1,VNum+1):
-        for m in range(1,PNum+1):
-            VBinSum[i-1] = VBinSum[i-1] + XBoxNumber[m-1,i-1] 
-        for m in range(1,PNum+1):
-            if VBinSum[i-1]>0:
-                VBoxPercent[m-1,i-1] = (XBoxNumber[m-1,i-1] / VBinSum[i-1])*100
-    # VBoxPercent PBoxPercent 左上-右下
-    # 将数据颠倒一下  左下-右上         第一行换为倒数第一行 方便可视化
-    InvXBoxNumber = np.zeros((PNum,VNum),dtype = int)
-    InvPBoxPercent = np.zeros((PNum,VNum),dtype = float)
-    InvVBoxPercent = np.zeros((PNum,VNum),dtype = float)
-    for m in range(1,PNum+1):
-        for n in range(1,VNum+1):
-            InvXBoxNumber[m-1,n-1] = XBoxNumber[PNum-(m-1)-1,n-1]
-            InvPBoxPercent[m-1,n-1] = PBoxPercent[PNum-(m-1)-1,n-1]
-            InvVBoxPercent[m-1,n-1] = VBoxPercent[PNum-(m-1)-1,n-1]
-    
-    #以水平功率带方向为准,分析每个水平功率带中,功率主带中心,即找百分比最大的网格位置。
-    PBoxMaxIndex = np.zeros((PNum,1),dtype = int)  #水平功率带最大网格位置索引
-    PBoxMaxP = np.zeros((PNum,1),dtype = float)       #水平功率带最大网格百分比
-    for m in range(1,PNum+1):
-        PBoxMaxIndex[m-1] = np.argmax(PBoxPercent[m-1, :])   #argmax返回最大值的索引
-        PBoxMaxP[m-1] = np.max(PBoxPercent[m-1, :])
-    #以垂直风速方向为准,分析每个垂直风速带中,功率主带中心,即找百分比最大的网格位置。
-    VBoxMaxIndex = np.zeros((VNum,1),dtype = int)  
-    VBoxMaxV = np.zeros((VNum,1),dtype = float)       
-    for m in range(1,VNum+1):
-        VBoxMaxIndex[m-1] = np.argmax(VBoxPercent[:, m-1])   
-        VBoxMaxV[m-1] = np.max(VBoxPercent[:, m-1])
-    
-    #切入风速特殊处理,如果切入风速过于偏右,向左拉回
-    if PBoxMaxIndex[0]>14:                     #第一个值对应的是风速最小处 即切入风速
-        PBoxMaxIndex[0] = 9 
-    #以水平功率带方向为基准,进行分析
-    DotDense = np.zeros(PNum)   #每一水平功率带的功率主带包含的网格数
-    DotDenseLeftRight = np.zeros((PNum,2))  #存储每一水平功率带的功率主带以最大网格为中心,向左,向右扩展的网格数
-    DotValve = 90  #从中心向左右对称扩展网格的散点百分比和的阈值。
-    PDotDenseSum = 0
-    for i in range(PNum - TopP):  # 从最下层水平功率带开始,向上分析到特定的功率带  
-        PDotDenseSum = PBoxMaxP[i]  # 以中心最大水平功率带为基准,向左向右对称扩展网格,累加各网格散点百分比  
-        iSpreadRight = 1  
-        iSpreadLeft = 1  
-          
-        while PDotDenseSum < DotValve:  
-            if (PBoxMaxIndex[i] + iSpreadRight) < VNum-1-1:  
-                PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[i] + iSpreadRight]  # 向右侧扩展  
-                iSpreadRight += 1  
-            else:
-                break  
-              
-            if (PBoxMaxIndex[i] - iSpreadLeft) > 0:  
-                PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[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  # 记录向左向右扩展的个数及每个功率仓内网格的个数  
-    # 此时DotDense和DotDenseLeftRight数组已经包含了所需信息    
-    #各行功率主带右侧宽度的中位数最具有代表性(因为先右后左)
-    DotDenseWidthLeft = np.zeros((PNum-TopP))
-    for i in range(PNum-TopP):
-        DotDenseWidthLeft[i] = DotDenseLeftRight[i,1]  #DotDenseLeftRight[i,1]:向右延伸个数
-    MainBandRight = np.median(DotDenseWidthLeft) #计算中位数
-    
-    # 初始化变量  
-    PowerLimit = np.zeros(PNum, dtype=int)  # 各水平功率带是否为限功率标识,1:是;0:不是  
-    WidthAverage = 0  # 功率主带右侧平均宽度  
-    WidthAverage_L = 0  # 功率主带左侧平均宽度  
-    WidthVar = 0  # 功率主带方差(此变量在提供的代码中并未使用)  
-    PowerLimitValve = 6  # 限功率主带判别阈值  
-    N_Pcount = 20  # 阈值  
-      
-    nCounterLimit = 0  # 限功率的个数  
-    nCounter = 0  # 正常水平功率带的个数  
-      
-    # 循环遍历水平功率带,从第1个到第PNum-TopP个  
-    for i in range(PNum - TopP):  
-        # 如果向右扩展网格数大于阈值,且该水平功率带点总数大于20,则标记为限功率带  
-        if (DotDenseLeftRight[i, 1] > PowerLimitValve) and (PBinSum[i] > N_Pcount):  
-            PowerLimit[i] = 1  
-            nCounterLimit += 1  #限功率的个数
-          
-        # 如果向右扩展网格数小于等于阈值,则累加右侧宽度(左侧宽度在代码中似乎有误)  
-        if DotDenseLeftRight[i, 1] <= PowerLimitValve:  
-            WidthAverage += DotDenseLeftRight[i, 1]  # 统计正常水平功率带右侧宽度
-            WidthAverage_L += DotDenseLeftRight[i,1]   #统计正常水平功率带左侧宽度
-            nCounter += 1  
-    # 计算平均宽度  
-    WidthAverage /= nCounter if nCounter > 0 else 1  # 避免除以0的情况  
-    WidthAverage_L /= nCounter if nCounter > 0 else 1   
-
-    #计算正常(即非限功率)水平功率带的功率主带宽度的方差,以此来反映从下到上宽度是否一致
-    WidthVar = 0  # 功率主带宽度的方差   
-    for i in range(PNum - TopP):  
-        # 如果向右扩展网格数小于等于阈值,则计算当前宽度与平均宽度的差值平方  
-        if DotDenseLeftRight[i, 1] <= PowerLimitValve:  
-            WidthVar += (DotDenseLeftRight[i, 1] - WidthAverage) ** 2  
-    # 计算方差(注意:除以nCounter-1是为了得到样本方差)  
-    WidthVar = np.sqrt(WidthVar / (nCounter - 1) if nCounter > 1 else 0)  # 避免除以0的情况
-
-    #各水平功率带,功率主带的风速范围,右侧扩展网格数*2*0.25
-    PowerBandWidth = WidthAverage*intervalwindspeed+WidthAverage_L*intervalwindspeed
-
-    # 对限负荷水平功率带的最大网格进行修正  
-    for i in range(1, PNum - TopP+1):  
-        if (PowerLimit[i] == 1) and (abs(PBoxMaxIndex[i] - PBoxMaxIndex[i - 1]) > 5):  
-            PBoxMaxIndex[i] = PBoxMaxIndex[i - 1] + 1  
-      
-    # 输出各层功率主带的左右边界网格索引  
-    DotDenseInverse = np.flipud(DotDenseLeftRight)  # 上下翻转数组以得到反向顺序  
-      
-    # 计算功率主带的左右边界  
-    CurveWidthR = np.ceil(WidthAverage) + 2  # 功率主带的右边界 + 2  
-    CurveWidthL = np.ceil(WidthAverage_L) + 2  # 功率主带的左边界 + 2  
-      
-    # 网格是否为限功率网格的标识数组  
-    BBoxLimit = np.zeros((PNum, VNum), dtype=int)  
-    # 标记限功率网格  
-    for i in range(2, PNum - TopP):  
-        if PowerLimit[i] == 1:
-            BBoxLimit[i, int(PBoxMaxIndex[i] + CurveWidthR + 1):VNum] = 1
-
-    # 初始化数据异常需要剔除的网格标识数组  
-    BBoxRemove = np.zeros((PNum, VNum), dtype=int)  
-    # 标记需要剔除的网格  
-    for m in range(PNum - TopP): 
-        for n in range(int(PBoxMaxIndex[m]) + int(CurveWidthR), VNum):  # 注意Python中的索引从0开始,因此需要减去1  
-            BBoxRemove[m, n] = 1 
-        # 功率主带左侧的超发网格,从最大索引向左直到第一个网格  
-        
-        for n in range(int(PBoxMaxIndex[m]) - int(CurveWidthL)+1, 0, -1):  # 使用range的步长参数来实现从右向左的迭代  
-            BBoxRemove[m, n-1] = 2  # 注意Python中的索引从0开始,因此需要减去1
-
-    # 初始化变量  
-    CurveTop = np.zeros((2, 1), dtype=int)  
-    CurveTopValve = 1  # 网格的百分比阈值  
-    BTopFind = 0  
-    mm = 0  
-    #确定功率主带的左上拐点,即额定风速位置的网格索引
-    CurveTop = np.zeros((2, 1), dtype=int)  
-    CurveTopValve = 1  # 网格的百分比阈值  
-    BTopFind = 0  
-    mm = 0   
-    for m in range(PNum - TopP, 0, -1):  # 注意Python的range是左闭右开区间,所以这里从PNum-TopP开始到1(不包括0)  
-        for n in range(int(np.floor(int(VCutIn) / intervalwindspeed)), VNum - 1):  # 使用floor函数来向下取整  
-            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 = m  # mm是拐点所在功率仓,对应其index
-                break  # 找到后退出内层循环  
-        if BTopFind == 1:  
-            break  # 找到后退出外层循环
-            
-    IsolateValve = 3  #功率主带右侧孤立点占比功率仓阈值 3%
-    # 遍历功率仓和网格  
-    for m in range(PNum - TopP):    
-        for n in range(int(PBoxMaxIndex[m]) + int(CurveWidthR), VNum):  
-            # 检查PBoxPercent是否小于阈值,如果是,则标记BBoxRemove为1  
-            if PBoxPercent[m, n] < IsolateValve:   
-                BBoxRemove[m, n] = 1
-    #功率主带顶部宽度
-    CurveWidthT = np.floor((maxP - PRated) / intervalP) + 1  
-    # 标记额定功率以上的超发点(PNum-PTop之间)  
-    for m in range(PNum - TopP, PNum):   
-        for n in range(VNum):  
-            BBoxRemove[m, n] = 3
-      
-    # 标记功率主带拐点左侧的欠发网格  
-    for m in range(mm-1, PNum - TopP): 
-        for n in range(int(CurveTop[1]) - 2):
-            BBoxRemove[m, n] = 2    # BBoxRemove数组现在包含了根据条件标记的超发点和欠发网格的信息
-
-    #以网格的标识,决定该网格内数据的标识。
-    # DzMarch809Sel数组现在包含了每个数据点的标识
-    DzMarch809Sel = np.zeros(nCounter1, dtype=int)  # 初始化标识数组   
-    nWhichP = 0  
-    nWhichV = 0  
-    nBadA = 0   
-    for i in range(nCounter1):  
-        for m in range( PNum ):   
-            if (DzMarch809[i, 1] > m * intervalP) and (DzMarch809[i, 1] <= (m+1) * intervalP):  
-                nWhichP = m  #m记录的是index
-                break  
-        for n in range( VNum ):  # 注意Python的range是左闭右开区间,所以这里到VNum+1  
-            if DzMarch809[i, 0] > ((n+1) * intervalwindspeed - intervalwindspeed/2) and DzMarch809[i, 0] <= ((n+1) * intervalwindspeed + intervalwindspeed / 2):  
-                nWhichV = n  #index
-                break  
-        if nWhichP >= 0 and nWhichV >= 0:  
-            if BBoxRemove[nWhichP, nWhichV] == 1:   
-                DzMarch809Sel[i] = 1  
-                nBadA += 1  
-            elif BBoxRemove[nWhichP, nWhichV] == 2:  
-                DzMarch809Sel[i] = 2  
-            elif BBoxRemove[nWhichP , nWhichV] == 3:  
-                DzMarch809Sel[i] = 0  # 额定风速以上的超发功率点认为是正常点,不再标识  
-    # DzMarch809Sel数组现在包含了每个数据点的标识
-
-    ##############################滑动窗口方法
-    # 存储限负荷数据  
-    PVLimit = np.zeros((nCounter1, 3))  #存储限负荷数据  %第3列用于存储限电的点所在的行数
-    nLimitTotal = 0  
-    nWindowLength = 6   #滑动窗口长度设置为6
-    LimitWindow = np.zeros(nWindowLength)  #滑动窗口空列表
-    UpLimit = 0    #上限
-    LowLimit = 0   #下限
-    PowerStd = 30  # 功率波动方差  
-    nWindowNum = np.floor(nCounter1/nWindowLength) #6587
-    PowerLimitUp = PRated - 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  
-    # PVLimit现在包含了限负荷数据,nLimitTotal是限负荷数据的总数
-    
-    
-    #将功率滑动窗口主带平滑化
-    # 初始化锯齿平滑的计数器  
-    nSmooth = 0  
-    # 遍历除了最后 TopP+1 个元素之外的所有 PBoxMaxIndex 元素  
-    for i in range(PNum - TopP - 1):  
-        PVLeftDown = np.zeros(2)  
-        PVRightUp = np.zeros(2)  
-        # 检查当前与下一个 PBoxMaxIndex 之间的距离是否大于等于1  
-        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  
-              
-            # 遍历 DzMarch809 数组  
-            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 是选中的点数
-    ###################################存储数据
-    # 存储好点  
-    nCounterPV = 0  # 初始化计数器  
-    PVDot = np.zeros((nCounter1, 3))  # 初始化存储好点的数组  nCounter1是p>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]  # 好点 Point_line记录nCounter1在原始数据中的位置 
-    nCounterVP = nCounterPV  
-     
-    # 对所有数据中的好点进行标注    
-    for i in range(nCounterVP):  
-        Labeled_March809[int(PVDot[i, 2] - 1), (SM[1]-1)] = 1  # 注意Python的索引从0开始,并且需要转换为整数索引  
-     
-    # 存储坏点  
-    nCounterBad = 0  # 初始化计数器  
-    PVBad = np.zeros((nCounter1, 3))  # 初始化存储坏点的数组  
-    for i in range(nCounter1):  
-        if DzMarch809Sel[i] in [1, 2, 3]:  
-            nCounterBad += 1  
-            PVBad[nCounterBad-1, :2] = DzMarch809[i, :2]  
-            PVBad[nCounterBad-1, 2] = Point_line[i]  
-        
-    # 对所有数据中的坏点进行标注  
-    for i in range(nCounterBad):  
-        Labeled_March809[int(PVBad[i, 2] - 1),(SM[1]-1)] = 5  # 坏点标识  
-
-    # 对所有数据中的限电点进行标注   
-    for i in range(nLimitTotal):  
-        Labeled_March809[int(PVLimit[i, 2] - 1),(SM[1]-1)] = 4  # 限电点标识  
-
-    # 对所有的数据点进行标注  
-    # Labeled_March809是array,提取所第四列的值保存为dataframe
-    A = Labeled_March809[:,3]
-    A=pd.DataFrame(A,columns=['lab'])
-    return A
-
-
-# scada_10min_category()
- 
-
-
-
-    
-    
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-    
-
-
-

+ 0 - 193
dataAnalysisBusiness/demo/SCADA_10min_category_2.py

@@ -1,193 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import matplotlib.pyplot as plt
-from matplotlib.pyplot import MultipleLocator
-import math
-
-
-intervalPower = 25  # For example
-intervalWindspeed = 0.25  # For example
-
-fieldRatedPower="额定功率"
-fieldRatedWindSpeed="额定风速"
-fieldWindSpeedCutIn="切入风速"
-fieldWindSpeedCutOut="切出风速"
-
-fieldTime="时间"
-fieldWindSpeed="风速"
-fieldActivePower="变频器电网侧有功功率"
-fieldLabel="lab"
-
-# 1. 数据加载和预处理函数
-def loadData(filePathSCADA:str, filePathTurbineInfo:str):
-    dataFrameSCADA = pd.read_csv(filePathSCADA, encoding="utf-8")
-    dataFrameTurbineInfo = pd.read_csv(filePathTurbineInfo)
-    return dataFrameSCADA, dataFrameTurbineInfo
-
-def extractTurbineParameters(turbineInfo:pd.DataFrame):
-    """
-    解析风电机组参数 
-
-    参数:
-        turbineInfo 风电机组信息DataFrame
-
-    返回:
-        PRated 额定功率(kw)
-        VCutOut 切出风速(m/s)
-        VCutIn 切入风速(m/s)
-        VRated 额定风速(m/s)
-    """
-    ratedPower = turbineInfo.loc[:, [fieldRatedPower]].values
-    windSpeedCutIn = turbineInfo.loc[:, [fieldWindSpeedCutIn]].values
-    windSpeedCutOut = turbineInfo.loc[:, [fieldWindSpeedCutOut]].values
-    ratedWindSpeed = turbineInfo.loc[:, [fieldRatedWindSpeed]].values
-
-    return ratedPower, windSpeedCutOut, windSpeedCutIn, ratedWindSpeed
-
-def preprocessData(dataFrameOfSCADA:pd.DataFrame):
-    """
-    获取机组SCADA数据的 时间、有功功率、风速,构建新的DataFrame变量
-
-    参数:
-        dataFrameOfSCADA 机组SCADA数据
-
-    返回:
-        由机组SCADA数据的 时间、有功功率、风速,构建新的DataFrame变量
-
-    """
-    timeStamp = dataFrameOfSCADA.loc[:, ['时间']]
-    activePower = dataFrameOfSCADA.loc[:, ['变频器电网侧有功功率']]
-    windSpeed = dataFrameOfSCADA.loc[:, ['风速']]
-    dataFramePartOfSCADA = pd.concat([timeStamp, activePower, windSpeed], axis=1)
-
-    dataFramePartOfSCADA[fieldLabel]=0
-    dataFramePartOfSCADA[fieldLabel]=dataFramePartOfSCADA[fieldLabel].astype(int)
-
-    return dataFramePartOfSCADA
-
-# 2. 数据标签分配和分箱计算
-def calculateIntervals(activePowerMax, ratedPower, windSpeedCutOut):
-    """
-    按有功功率(以25kw为间隔)、风速(以0.25m/s为间隔)分仓
-
-    参数:
-        max_power 当前机组的有功功率最大值
-        PRated  机组额定功率
-        wind_speed_cutout  切出风速
-
-    返回:
-        interval_power 有功功率分仓间隔
-        interval_windspeed 风速分仓间隔
-        PNum  有功功率分仓数量
-        VNum 风速分仓数量
-    """
-    binNumOfPower = math.floor(activePowerMax / intervalPower) + 1 if activePowerMax >= ratedPower else math.floor(ratedPower / intervalPower)
-    binNumOfWindSpeed = math.ceil(windSpeedCutOut / intervalWindspeed)
-
-    return binNumOfPower, binNumOfWindSpeed
-
-def labelData(dataFramePartOfSCADA:pd.DataFrame, conditions):
-    """
-    根据特定条件对数据进行标签分配,例如功率和风速阈值。
-    
-    参数:
-        LM (DataFrame): 包含功率和风速数据的DataFrame。
-        conditions (dict): 字典,键为条件名称,值为相应的阈值。
-    
-    返回:
-        DataFrame: 带有新的'label'列的原始DataFrame。
-    """
-    # 初始化标签列
-    dataFramePartOfSCADA['label'] = 0
-    
-    # 根据条件进行数据标签分配
-    for condition, threshold in conditions.items():
-        if condition == 'power_below':
-            dataFramePartOfSCADA.loc[dataFramePartOfSCADA[fieldActivePower] <= threshold, 'label'] = -1
-        elif condition == 'power_above':
-            dataFramePartOfSCADA.loc[dataFramePartOfSCADA[fieldActivePower] >= threshold, 'label'] = 1
-    
-    return dataFramePartOfSCADA
-
-def computeBins(data, intervals):
-    """为给定数据计算统计箱。
-    
-    参数:
-        data (DataFrame): 需要进行分箱的数据。
-        intervals (dict): 字典,为每个列指定间隔大小。
-    
-    返回:
-        DataFrame: 分箱数据作为区间内的计数或百分比。
-    """
-    binsResults = {}
-    for column, interval in intervals.items():
-        minValue = data[column].min()
-        maxValue = data[column].max()
-        bins = np.arange(minValue, maxValue + interval, interval)
-        binnedData = pd.cut(data[column], bins, include_lowest=True)
-        binCounts = pd.value_counts(binnedData, sort=False)
-        binsResults[column] = binCounts
-    
-    return pd.DataFrame(binsResults)
-
-# 3. 应用标签函数
-def applyLabels(data, labels):
-    """根据外部或计算出的标签对数据应用标签。
-    
-    参数:
-        data (DataFrame): 需要应用标签的数据。
-        labels (Series或array): 应用的标签;必须与数据的索引或长度相匹配。
-    
-    返回:
-        DataFrame: 应用标签后的数据。
-    """
-    data['label'] = labels
-    return data
-
-# 4. 数据可视化
-def plot_data(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.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()
-
-# 5. Main Execution
-def main():
-    turbine=82
-    filePathSCADA = r'E:\BaiduNetdiskDownload\test\min_scada_LuoTuoGou\72\{}.csv'.format(turbine)
-    filePathTurbineInfo = r'E:\BaiduNetdiskDownload\test\min_scada_LuoTuoGou\72\info.csv'
-    outputFilePathOfSCADA=r"E:\BaiduNetdiskDownload\test\min_scada_LuoTuoGou\72\labeled\labeled_{}.csv".format(turbine)
-
-    dataFrameOfSCADA, turbineInfo = loadData(filePathSCADA, filePathTurbineInfo)
-    ratedPower, windSpeedCutOut, windSpeedCutIn, ratedWindSpeed = extractTurbineParameters(turbineInfo)
-    dataFramePartOfSCADA = preprocessData(dataFrameOfSCADA)
-
-    powerMax=dataFramePartOfSCADA[fieldActivePower].max()
-    binNumOfPower, binNumOfWindSpeed=calculateIntervals(powerMax,ratedPower,windSpeedCutOut)
-    
-    # 根据功率阈值对数据进行标签分配
-    conditions = {'power_below': 10, 'power_above': ratedPower[0][0]}
-    labeledData = labelData(dataFramePartOfSCADA, conditions)
-    
-    # 为功率和风速计算分箱
-    intervals = {fieldActivePower: 100, fieldWindSpeed: 1}
-    binnedData = computeBins(labeledData, intervals)
-    
-    # 应用标签(假设某些外部标签被提供或在其他地方计算)
-    externalLabels = np.random.choice([0, 1], size=len(labeledData))  # 随机示例
-    labeledData = applyLabels(labeledData, externalLabels)
-
-    labeledData.to_csv(outputFilePathOfSCADA)
-    
-    plot_data(labeledData[fieldWindSpeed], labeledData[fieldActivePower])
-
-if __name__ == '__main__':
-    main()

+ 0 - 632
dataAnalysisBusiness/demo/SCADA_10min_category_3.py

@@ -1,632 +0,0 @@
-import os
-import pandas as pd
-import numpy as np
-import matplotlib.pyplot as plt
-from matplotlib.pyplot import MultipleLocator
-import math
-import pdb
-# pdb.set_trace()  # 设置断点
-
-intervalPower = 25  # For example
-intervalWindspeed = 0.25  # For example
-
-fieldRatedPower="额定功率"
-fieldRatedWindSpeed="额定风速"
-fieldWindSpeedCutIn="切入风速"
-fieldWindSpeedCutOut="切出风速"
-
-fieldTime="时间"
-fieldWindSpeed="风速"
-fieldActivePower="变频器电网侧有功功率"
-fieldLabel="lab"
-
-# 1. 数据加载和预处理函数
-def loadData(filePathSCADA:str, filePathTurbineInfo:str):
-    dataFrameSCADA = pd.read_csv(filePathSCADA, encoding="utf-8")
-    dataFrameTurbineInfo = pd.read_csv(filePathTurbineInfo)
-    return dataFrameSCADA, dataFrameTurbineInfo
-
-def extractTurbineParameters(turbineInfo:pd.DataFrame):
-    """
-    解析风电机组参数 
-
-    参数:
-        turbineInfo 风电机组信息DataFrame
-
-    返回:
-        PRated 额定功率(kw)
-        VCutOut 切出风速(m/s)
-        VCutIn 切入风速(m/s)
-        VRated 额定风速(m/s)
-    """
-    ratedPower = turbineInfo.loc[:, [fieldRatedPower]].values
-    windSpeedCutIn = turbineInfo.loc[:, [fieldWindSpeedCutIn]].values
-    windSpeedCutOut = turbineInfo.loc[:, [fieldWindSpeedCutOut]].values
-    ratedWindSpeed = turbineInfo.loc[:, [fieldRatedWindSpeed]].values
-
-    return ratedPower, windSpeedCutOut, windSpeedCutIn, ratedWindSpeed
-
-def preprocessData(dataFrameOfSCADA:pd.DataFrame):
-    """
-    获取机组SCADA数据的 时间、有功功率、风速,构建新的DataFrame变量
-
-    参数:
-        dataFrameOfSCADA 机组SCADA数据
-
-    返回:
-        由机组SCADA数据的 时间、有功功率、风速,构建新的DataFrame变量
-
-    """
-    timeStamp = dataFrameOfSCADA.loc[:, ['时间']]
-    activePower = dataFrameOfSCADA.loc[:, ['变频器电网侧有功功率']]
-    windSpeed = dataFrameOfSCADA.loc[:, ['风速']]
-    dataFramePartOfSCADA = pd.concat([timeStamp,activePower,windSpeed], axis=1)
-
-    # dataFramePartOfSCADA[fieldLabel]=0
-    # dataFramePartOfSCADA[fieldLabel]=dataFramePartOfSCADA[fieldLabel].astype(int)
-
-    return dataFramePartOfSCADA
-
-    
-# 2. 数据标签分配和分箱计算
-def calculateIntervals(activePowerMax, ratedPower, windSpeedCutOut):
-    """
-    按有功功率(以25kw为间隔)、风速(以0.25m/s为间隔)分仓
-
-    参数:
-        max_power 当前机组的有功功率最大值
-        PRated  机组额定功率
-        wind_speed_cutout  切出风速
-
-    返回:
-        interval_power 有功功率分仓间隔
-        interval_windspeed 风速分仓间隔
-        PNum  有功功率分仓数量
-        VNum 风速分仓数量
-    """
-    binNumOfPower = math.floor(activePowerMax / intervalPower) + 1 if activePowerMax >= ratedPower else math.floor(ratedPower / intervalPower)
-    binNumOfWindSpeed = math.ceil(windSpeedCutOut / intervalWindspeed)
-
-    return binNumOfPower, binNumOfWindSpeed
-
-def calculateTopP(activePowerMax,ratedPower):
-    """
-    计算额定功率以上功率仓的个数
-
-    参数:
-        max_power 当前机组的有功功率最大值
-        PRated  机组额定功率
-        
-    返回:
-        TopP 额定功率以上功率仓的个数
-    """
-    TopP = 0   
-    if activePowerMax >= ratedPower: 
-        TopP = math.floor((activePowerMax - ratedPower) / intervalPower) + 1  
-    else:  
-        TopP = 0   
-    return TopP
-
-def chooseData(dataFramePartOfSCADA:pd.DataFrame, dataFrameOfSCADA):
-    """
-    根据特定条件对数据进行标签分配,例如功率和风速阈值。
-    
-    参数:
-        dataFramePartOfSCADA (DataFrame): 包含时间和功率和风速数据的DataFrame。
-        dataFrameOfSCADA: 原始数据
-    
-    返回:
-        DzMarch809: array:V P lab: 38181。
-        nCounter1: 个数
-        dataFramePartOfSCADA: 
-    """
-    # 初始化标签列
-    SM1 = dataFramePartOfSCADA.shape #(52561,3)
-    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 = dataFrameOfSCADA[fieldActivePower]
-    WSpeed = dataFrameOfSCADA[fieldWindSpeed]
-
-    for i in range(AA):
-        if (APower[i] > 10) & (WSpeed[i] > 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(binNumOfWindSpeed,binNumOfPower,nCounter1,DzMarch809):
-    """
-    统计各网格中落入label!=-1的数据点个数
-    
-    参数:
-        binNumOfWindSpeed: 风速分仓个数。
-        binNumOfPower: 功率分仓个数。
-        DataFrame: 带有新的'label'列的原始DataFrame。
-        nCounter1: 数据个数
-        DzMarch809
-    返回:
-        XBoxNumber: 各网格中落入label!=-1的数据点个数的array。
-    """
-    # 遍历有效数据
-    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(XBoxNumber, binNumOfPower, binNumOfWindSpeed,axis):
-    """
-    计算分仓(水平/竖直)后每个网格占百分比
-    
-    参数:
-        XBoxNumber: 各网格中落入label!=-1的数据点个数的array。
-        binNumOfPower: 功率分仓个数。
-        binNumOfWindSpeed: 风速分仓个数。
-        axis: "power"or"speed"分仓
-    返回:
-        BoxPercent: 占比情况array。
-    """
-    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(BoxPercent, binNumOfPower, binNumOfWindSpeed, axis):
-    """
-    计算分仓(水平/竖直)后占百分比最大的网格索引及值
-    
-    参数:
-        BoxPercent: 占比情况array。
-        binNumOfPower: 功率分仓个数。
-        binNumOfWindSpeed: 风速分仓个数。
-        axis: "power"or"speed"分仓
-    返回:
-        BoxMaxIndex: 占百分比最大的网格索引。
-        BoxMax: 占百分比最大的网格值
-    """
-    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(m, BoxMax,TopP,BoxMaxIndex,BoxPercent,binNumOfPower,binNumOfWindSpeed):
-    """
-    以中心最大水平功率带为基准,向两侧对称扩展网格,使网格散点百分比总值达到阈值m
-    
-    参数:
-        m: 设定总和百分比阈值。
-        BoxMax: 占百分比最大的网格值。
-        TopP: 额定功率以上功率仓个数。
-        BoxMaxIndex: 占百分比最大的网格索引。
-        BoxPercent: 占比情况array。
-        binNumOfPower: 功率分仓个数。
-        binNumOfWindSpeed: 风速分仓个数。
-    返回:
-        DotDense: 每个功率仓内网格的个数。
-        DotDenseLeftRight: 向左向右拓展的网格个数
-    """
-    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(binNumOfPower,TopP,DotDenseLeftRight,PBinSum):
-    """
-    计算功率主带的平均宽度
-    
-    参数:
-        binNumOfPower: 功率分仓个数。
-        TopP: 额定功率以上功率仓个数。
-        DotDenseLeftRight: 向左向右拓展的网格个数    
-        PBinSum: 功率仓内数据点总和
-    返回:
-        DotDense: 每个功率仓内网格的个数。
-        DotDenseLeftRight: 向左向右拓展的网格个数
-        PowerLimit: 各水平功率带是否为限功率标识,1:是;0:不是
-    """
-
-    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(binNumOfPower,TopP,PowerLimit,BoxMaxIndex):
-    """
-    对限负荷水平功率带的最大网格进行修正
-    
-    参数:
-        binNumOfPower: 功率分仓个数。
-        TopP: 额定功率以上功率仓个数。
-        PowerLimit:标识限功率水平功率带,1:是;0:不是
-        BoxMaxIndex: 占百分比最大的网格索引
-    返回:
-        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(binNumOfPower,binNumOfWindSpeed,TopP,CurveWidthR,CurveWidthL,BoxMaxIndex):
-    '''
-    标记需剔除的网格
-    
-    参数:
-        binNumOfPower: 功率分仓个数。
-        binNumOfWindSpeed:风速分仓个数
-        TopP: 额定功率以上功率仓个数。
-        CurveWidthR:功率主带轮廓
-        CurveWidthL
-        BoxMaxIndex: 修正后的最大占比网格索引
-    返回:
-        BBoxRemove: 标识需剔除的网格
-    '''
-    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(binNumOfPower,binNumOfWindSpeed,TopP,CurveWidthR,PowerLimit,BoxPercent,BoxMaxIndex,mm,BBoxRemove,nn):
-    '''
-    标记限功率网格 
-    1:右侧欠发 2:左侧超发 3:额定功率以上超发
-    
-    参数:
-        binNumOfPower: 功率分仓个数。
-        binNumOfWindSpeed:风速分仓个数
-        TopP: 额定功率以上功率仓个数。
-        CurveWidthR:功率主带轮廓
-        PowerLimit: 标识限功率水平功率带,1:是;0:不是
-        BoxMaxIndex: 修正后的最大占比网格索引
-        mm: 拐点所在功率仓
-        BBoxRemove:需剔除的网格
-        CurveTop1:拐点对应列
-    返回:
-        BBoxLimit:标识限功率网格
-    '''
-    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-1, binNumOfPower - TopP): 
-        for n in range(int(nn) - 2):
-            BBoxRemove[m, n] = 2
-    
-    return BBoxLimit
-    
-def markData(binNumOfPower, binNumOfWindSpeed,DzMarch809,BBoxRemove,nCounter1):
-    '''
-    根据网格标识来标记数据点
-    
-    参数:
-        nCounter1
-        binNumOfPower: 功率分仓个数。
-        binNumOfWindSpeed:风速分仓个数
-        DzMarch809: array V P lab: 38181。
-        BBoxRemove:需剔除的网格
-        
-    返回:
-        DzMarch809Sel:数组现在包含了每个数据点的标识
-    '''
-    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(nCounter1,ratedPower,DzMarch809,DzMarch809Sel,Point_line):
-    '''
-    滑动窗口方法,进一步标记数据坏点
-    
-    参数:
-        nCounter1:
-        ratedPower:
-        Point_line:
-        
-    返回:
-        PVLimit: 限负荷数据
-        nLimitTotal: 是限负荷数据的总数
-    '''
-
-    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(DzMarch809, DzMarch809Sel,Point_line, nCounter1):  
-    """  
-    存储好点,并返回存储好的点的数组和计数。
-    
-    参数:
-        DzMarch809: array:V P lab: 38181。
-        DzMarch809Sel: 数组现在包含了每个数据点的标识
-        Point_line:
-        nCounter1:
-        axis: 'good' or 'bad'
-        
-    返回:
-        PVDot: 数据
-        nCounterPV: 数据个数
-
-    """  
-    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(nCounterPV,nCounterBad,dataFramePartOfSCADA,PVDot,PVBad,SM,nLimitTotal,PVLimit):
-    """  
-    标记好点、坏点、限电点。
-    
-    参数:
-        nCounterPV
-        nCounterBad
-        dataFramePartOfSCADA
-        PVDot
-        PVBad
-        SM
-        nLimitTotal
-        PVLimit
-        
-    返回:
-        dataFramePartOfSCADA
-
-    """  
-
-    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 plot_data(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.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()
-
-# 5. Main Execution
-def main():
-    turbine=85
-    basePath=r'E:\BaiduNetdiskDownload\test\min_scada_LuoTuoGou\72'
-    filePathSCADA = r'{}\{}.csv'.format(basePath,turbine)
-    filePathTurbineInfo = r'{}\info.csv'.format(basePath)
-    outputFilePathOfSCADA=r"{}\labeled\labeled_{}.csv".format(basePath,turbine)
-
-    dataFrameOfSCADA, turbineInfo = loadData(filePathSCADA, filePathTurbineInfo)
-    ratedPower, windSpeedCutOut, windSpeedCutIn, ratedWindSpeed = extractTurbineParameters(turbineInfo)
-    dataFramePartOfSCADA = preprocessData(dataFrameOfSCADA)
-    powerMax=dataFramePartOfSCADA[fieldActivePower].max()
-
-    binNumOfPower, binNumOfWindSpeed = calculateIntervals(powerMax,ratedPower,windSpeedCutOut)
-    TopP = calculateTopP(powerMax,ratedPower)
-    # 根据功率阈值对数据进行标签分配
-    DzMarch809,nCounter1,dataFramePartOfSCADA,Point_line,SM = chooseData(dataFramePartOfSCADA, dataFrameOfSCADA)  
-    XBoxNumber = gridCount(binNumOfWindSpeed,binNumOfPower,nCounter1,DzMarch809)
-    PBoxPercent,PBinSum = percentageDots(XBoxNumber, binNumOfPower, binNumOfWindSpeed, 'power')
-    VBoxPercent,VBinSum = percentageDots(XBoxNumber, binNumOfPower, binNumOfWindSpeed, 'speed')
-
-    PBoxMaxIndex, PBoxMaxP = maxBoxPercentage(PBoxPercent, binNumOfPower, binNumOfWindSpeed, 'power')
-    VBoxMaxIndex, VBoxMaxV = maxBoxPercentage(VBoxPercent, binNumOfPower, binNumOfWindSpeed, 'speed')
-    if PBoxMaxIndex[0] > 14: PBoxMaxIndex[0] = 9
-    DotDenseLeftRight = extendBoxPercent(90, PBoxMaxP,TopP,PBoxMaxIndex,PBoxPercent,binNumOfPower,binNumOfWindSpeed)
-    # pdb.set_trace()  # 设置断点
-    WidthAverage, WidthAverage_L,PowerLimit = calculatePWidth(binNumOfPower,TopP,DotDenseLeftRight,PBinSum)
-    PBoxMaxIndex = 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  
-    for m in range(binNumOfPower - TopP, 0, -1):
-        for n in range(int(np.floor(int(windSpeedCutIn) / 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 = m
-                nn = n
-                break 
-        if BTopFind == 1:  
-            break 
-    #标记网格
-    BBoxRemove = markBoxLimit(binNumOfPower,binNumOfWindSpeed,TopP,CurveWidthR,CurveWidthL,PBoxMaxIndex)
-    BBoxLimit = markBoxPLimit(binNumOfPower,binNumOfWindSpeed,TopP,CurveWidthR,PowerLimit,PBoxPercent,PBoxMaxIndex,mm,BBoxRemove,nn)
-    DzMarch809Sel = markData(binNumOfPower, binNumOfWindSpeed,DzMarch809,BBoxRemove,nCounter1)
-    PVLimit,nLimitTotal = windowFilter(nCounter1,ratedPower,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 = store_points(DzMarch809, DzMarch809Sel,Point_line, nCounter1)
-    #标注   
-    dataFramePartOfSCADA = markAllData(nCounterPV,nCounterBad,dataFramePartOfSCADA,PVDot,PVBad,SM,nLimitTotal,PVLimit)
-    A = dataFramePartOfSCADA[:,3]
-    A=pd.DataFrame(A,columns=['lab'])
-
-    labeledData = pd.concat([dataFrameOfSCADA,A],axis=1)
-    D = labeledData[labeledData['lab'].isin([-1,0,1,2,3,4,5])]#选择为1的行
-    labeledData.to_csv(outputFilePathOfSCADA,encoding='utf-8')
-    plot_data(D[fieldWindSpeed], D[fieldActivePower])
-
-
-if __name__ == '__main__':
-    main()

+ 0 - 62
dataAnalysisBusiness/demo/scatter3D_plotly.py

@@ -1,62 +0,0 @@
-import pandas as pd  
-import plotly.graph_objects as go  
-  
-# 示例数据  
-data = {  
-    '机组名': ['机组A', '机组B', '机组C', '机组D'],  
-    '时间': ['2024-01-09 09:13:29', '2024-01-10 10:14:30', '2024-02-09 08:13:29', '2024-02-10 09:14:30'],  
-    '年月': ['2024-01', '2024-01', '2024-02', '2024-02'],  
-    '风速': [5.0, 6.0, 4.5, 5.5],  
-    '有功功率': [1000, 1200, 900, 1100]  
-}  
-  
-df = pd.DataFrame(data)  
-  
-# 按风速升序排列数据  
-df_sorted = df.sort_values(by='风速')  
-  
-# 获取唯一年月  
-unique_months = df_sorted['年月'].unique()  
-  
-# 自定义颜色列表(确保颜色数量与唯一月份的数量相匹配)  
-colors = ['red', 'blue', 'green', 'purple']  # 根据实际唯一月份数量调整颜色数量  
-  
-# 创建颜色映射  
-color_map = dict(zip(unique_months, colors))  
-  
-# 使用go.Scatter3d创建3D散点图  
-trace = go.Scatter3d(  
-    x=df_sorted['风速'],  
-    y=df_sorted['有功功率'],  
-    z=[color_map[month] for month in df_sorted['年月']],  
-    mode='markers',  
-    marker=dict(  
-        color=[color_map[month] for month in df_sorted['年月']],  
-        size=10,  
-        line=dict(color='rgba(255, 255, 255, 0.8)', width=0.5),  
-        opacity=0.8  
-    )  
-)  
-  
-# 创建图形  
-fig = go.Figure(data=[trace])  
-  
-# 更新图形的布局  
-fig.update_layout(  
-    title='按风速升序排列的3D散点图:风速、有功功率与年月',  
-    margin=dict(l=0, r=0, b=0, t=0),  
-    scene=dict(  
-        xaxis=dict(title='风速'),  
-        yaxis=dict(title='有功功率'),  
-        zaxis=dict(  
-            title='年月',  
-            tickmode='array',  
-            tickvals=unique_months,  
-            ticktext=unique_months,  
-            categoryorder='category ascending'  
-        )  
-    )  
-)  
-  
-# 显示图形  
-fig.show()

+ 0 - 50
dataAnalysisBusiness/demo/scatter3D_plotly_make_subplots.py

@@ -1,50 +0,0 @@
-import pandas as pd  
-import plotly.graph_objects as go  
-from plotly.subplots import make_subplots  
-  
-# 假设你的DataFrame叫做df,并且已经包含了所需字段  
-# 如果你的数据是CSV文件,可以使用pd.read_csv('your_file.csv')来加载数据  
-# df = pd.read_csv('your_file.csv')  
-  
-# 示例数据  
-data = {  
-    '机组名': ['机组A', '机组B', '机组C', '机组D'],  
-    '时间': ['2024-01-09 09:13:29', '2024-01-10 10:14:30', '2024-02-09 08:13:29', '2024-02-10 09:14:30'],  
-    '年月': ['2024-01', '2024-01', '2024-02', '2024-02'],  
-    '风速': [5.0, 6.0, 4.5, 5.5],  
-    '有功功率': [1000, 1200, 900, 1100]  
-}  
-  
-df = pd.DataFrame(data)  
-  
-# 创建颜色映射,将每个年月映射到一个唯一的颜色  
-unique_months = df['年月'].unique()  
-colors = [f'rgb({i}, {150 - i}, 50)' for i in range(len(unique_months))]  
-color_map = dict(zip(unique_months, colors))  
-  
-# 使用make_subplots创建3D散点图  
-fig = make_subplots(rows=1, cols=1, specs=[[{"type": "scatter3d"}]])  
-  
-# 遍历DataFrame的每一行,为每个点添加数据  
-for index, row in df.iterrows():  
-    x = row['风速']  
-    y = row['年月']  
-    z = row['有功功率']  
-    color = color_map[y]  
-      
-    # 添加散点到子图  
-    fig.add_trace(go.Scatter3d(x=[x], y=[y], z=[z], mode='markers', marker=dict(color=color)), row=1, col=1)  
-  
-# 更新子图的布局,设置y轴为category类型,并设置其类别顺序  
-fig.update_layout(  
-    title='3D散点图:风速、年月与有功功率',  
-    margin=dict(l=0, r=0, b=0, t=0),  
-    scene=dict(  
-        xaxis=dict(title='风速'),  
-        yaxis=dict(title='年月', tickmode='array', tickvals=unique_months, ticktext=unique_months, categoryorder='category ascending'),  
-        zaxis=dict(title='有功功率')  
-    )  
-)  
-  
-# 显示图形  
-fig.show()

+ 0 - 19
dataAnalysisBusiness/demo/test.py

@@ -1,19 +0,0 @@
-import plotly.express as px
-import pandas as pd
-
-# 创建一个示例数据框架,包含单月数据
-data_single_month = {
-    '时间': ['2023-03', '2023-03', '2023-03', '2023-03', '2023-03'],
-    '发电机转速': [1000, 1500, 2000, 2500, 3000],
-    '功率': [120, 180, 160, 210, 230]
-}
-
-df_single = pd.DataFrame(data_single_month)
-
-# 绘制3D散点图
-fig_single = px.scatter_3d(df_single, x='发电机转速', y='时间', z='功率', 
-                           title='3D 散点图 - 单月数据',
-                           labels={'发电机转速': '转速', '时间': '月份', '功率': '输出功率'})
-
-# 显示图形
-fig_single.show()

+ 0 - 113
dataAnalysisBusiness/demo/testDataProcess.py

@@ -1,113 +0,0 @@
-import os  
-import pandas as pd  
-import numpy as np
-import matplotlib.pyplot as plt  
-  
-def process_scada_data(fpath, turbine_number, fn_start, fn_end, status_normal):  
-    """  
-    处理SCADA数据的函数。  
-      
-    参数:  
-        fpath (str): 文件存放位置的路径。  
-        turbine_number (int): 风机数量(尽管此参数在此函数中未使用,但可以保留以匹配MATLAB代码)。  
-        fn_start (int): 开始处理的文件编号。  
-        fn_end (int): 结束处理的文件编号(不包含)。  
-        status_normal (int): 风机正常并网状态的状态字(尽管此参数在此函数中未使用,但可以保留以匹配MATLAB代码)。  
-    """  
-    # 循环处理每个文件  
-    for fn in range(fn_start, fn_end):  
-        fname = os.path.join(fpath, f"{fn}.csv")  
-          
-        # 读取CSV文件  
-        scada_10min = pd.read_csv(fname)  
-          
-        # 提取所需列  
-        time_stamp = scada_10min["时间"]  
-        active_power = scada_10min["变频器电网侧有功功率"]  
-        wind_speed = scada_10min["风速"]  
-          
-        # 创建包含所需列的DataFrame  
-        LM = pd.DataFrame({  
-            "时间戳": time_stamp,  
-            "有功功率": active_power,  
-            "风速": wind_speed  
-        })  
-          
-        # 调用数据标签处理函数(需要您根据MATLAB实现来编写此函数)  
-        xx = data_label(LM,fpath)  
-          
-        # 合并标签数据到原始DataFrame  
-        merged_df = pd.concat([scada_10min, xx], axis=1)  
-          
-        # 筛选出标签为1的行  
-        D = merged_df[merged_df["lab"] == 1]  
-          
-        # 绘制散点图  
-        plt.scatter(D["风速"], D["变频器电网侧有功功率"], s=50, fillstyle='full')  
-        plt.title(f"风机 {fn} 散点图")  
-        plt.xlabel("风速")  
-        plt.ylabel("变频器电网侧有功功率")  
-        plt.show()  
-          
-        # 创建保存结果的目录(如果不存在)  
-        labeled_dir = os.path.join(fpath, "labeled")  
-        os.makedirs(labeled_dir, exist_ok=True)  
-          
-        # 将处理后的数据保存到CSV文件  
-        labeled_fname = os.path.join(labeled_dir, f"{fn}_10s_n.csv")  
-        merged_df.to_csv(labeled_fname, index=False)  
-  
-# 假设data_label函数已经实现,这里只是一个示例的占位符  
-def data_label(df:pd.DataFrame,fpath):  
-    # 在这里实现您的数据标签处理逻辑  
-    # 返回带有新标签的Series或DataFrame  
-     # 读取风机参数数据
-    fname2 = fpath + "info.csv"
-    turbine_info = pd.read_csv(fname2, keep_default_na=False)
-    PRated = turbine_info["额定功率"].values[0]
-    VCutOut = turbine_info["切出风速"].values[0]
-    VCutIn = turbine_info["切入风速"].values[0]
-    VRated = turbine_info["额定风速"].values[0]
-    
-    # 读入有功功率和风速数据
-    Labeled_March809 = df
-    APower = Labeled_March809["active_power"]
-    WSpeed = Labeled_March809["wind_speed"]
-
-    # 初始化计算用的变量
-    maxP = APower.max()
-    intervalP = 25  # 功率分区间隔为25
-    intervalwindspeed = 0.25  # 风速分区间隔为0.25m/s
-    
-    # 根据最大功率和额定功率,计算功率和风速的区间数
-    PNum = (maxP // intervalP) + 1 if maxP >= PRated else (PRated // intervalP)
-    TopP = ((maxP - PRated) // intervalP) + 1 if maxP >= PRated else 0
-    VNum = np.ceil(VCutOut / intervalwindspeed).astype(int)
-
-    # 初始化标签列
-    Labeled_March809['label'] = 0
-
-    # 数据预处理:标记功率小于等于10的点
-    Labeled_March809.loc[APower <= 10, 'label'] = -1
-
-    # 下面是逻辑处理的示例,涉及到循环、条件判断和数据标记
-    # 示例:标记风速和功率在特定范围内的点
-    for i, row in Labeled_March809.iterrows():
-        if row['active_power'] > 10 and row['wind_speed'] > 0:
-            # 这里可以根据需要添加更多的处理逻辑
-            pass
-
-    # 以下是更高级的数据处理示例,这部分代码需要您根据实际逻辑继续开发
-    # 示例:根据风速和功率的分布对数据进行进一步的标记
-    # 请注意,这里需要你根据上面 MATLAB 代码的具体逻辑来实现相应的Python代码
-
-    return Labeled_March809
-  
-# 设置文件路径和其他参数  
-fpath = "E:\\BaiduNetdiskDownload\\test\\min_scada_LuoTuoGou\\72\\"  
-# 注意:turbine_number 在此函数中未使用,但保持以匹配MATLAB代码  
-turbine_number = 24  
-status_normal = 8  
-  
-# 调用函数处理文件,假设从编号82的文件开始,只处理这一个文件  
-process_scada_data(fpath, turbine_number, 82, 83, status_normal)

+ 0 - 12
dataAnalysisBusiness/demo/testPandas.py

@@ -1,12 +0,0 @@
-import pandas as pd
-import numpy as np
-
-df=pd.read_csv(r"E:/BaiduNetdiskDownload/DTSXJK_WJWFC_Q1_W001_2023-10-01_last_1seconds.csv",header=1)
-
-print(df.head())
-
-
-df["WNAC_WdDir"]=df["WNAC_WdDir"].astype("Float32")
-df["弧度"]=df["WNAC_WdDir"]/360*2*np.pi
-
-print(df.head())

+ 1 - 2
dataAnalysisBusiness/setup.py

@@ -5,6 +5,5 @@ setup(
     version='1.0.202403180918',
     description='Data Analysis Business Package', # 描述信息
     author='Xie Zhou Yang', # 作者
-    packages=find_packages(),
-    exclude_package_data={'': ['algorithm/*.py','common/*.py']},  # 另一种排除源代码的方式
+    packages=find_packages()
 )

+ 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 - 9
dataContract/setup.py

@@ -1,9 +0,0 @@
-from setuptools import setup, find_packages
-
-setup(
-    name='dataContract',
-    version='1.0.202403180918',
-    description='Data Contract Package', # 描述信息
-    author='Xie Zhou Yang', # 作者
-    packages=find_packages()
-)

+ 0 - 0
dataAnalysisBehavior/common/__init__.py → repository/__init__.py


+ 1 - 1
repositoryZN/setup.py → repository/setup.py

@@ -1,7 +1,7 @@
 from setuptools import setup, find_packages
 
 setup(
-    name='repositoryZN',
+    name='repository',
     version='1.0.202403180918',
     description='Repository Package', # 描述信息
     author='Xie Zhou Yang', # 作者

+ 0 - 0
dataContract/algorithmContract/__init__.py → repository/utils/__init__.py


+ 0 - 0
repositoryZN/utils/csvFileUtil.py → repository/utils/csvFileUtil.py


+ 0 - 0
repositoryZN/utils/directoryUtil.py → repository/utils/directoryUtil.py


+ 0 - 0
repositoryZN/utils/jsonUtil.py → repository/utils/jsonUtil.py


+ 0 - 4
repositoryZN/__init__.py

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

+ 0 - 0
repositoryZN/utils/__init__.py → wtoaamapi/apps/business/algorithm/__init__.py


+ 2 - 2
dataAnalysisBehavior/behavior/analyst.py → wtoaamapi/apps/business/algorithm/analyst.py

@@ -2,8 +2,8 @@ from .baseAnalyst import BaseAnalyst
 import os
 import pandas as pd
 import numpy as np
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
 
 
 class Analyst(BaseAnalyst):

+ 11 - 0
wtoaamapi/apps/business/algorithm/analystWithNoCustomeFilter.py

@@ -0,0 +1,11 @@
+from .baseAnalyst import BaseAnalyst
+import os
+import pandas as pd
+import numpy as np
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
+
+
+class AnalystWithNoCustomFilter(BaseAnalyst):
+    def typeAnalyst(self):
+        pass

+ 49 - 0
wtoaamapi/apps/business/algorithm/baseAnalyst.py

@@ -0,0 +1,49 @@
+from abc import ABC, abstractmethod
+import pandas as pd
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
+from .commonBusiness import CommonBusiness
+
+
+class BaseAnalyst(ABC):
+    def __init__(self, confData: ConfBusiness):
+        self.common=CommonBusiness()
+        self.confData = confData
+
+    @abstractmethod
+    def typeAnalyst(self):
+        pass
+
+    def getOutputAnalysisDir(self):
+        """
+        获取当前分析的输出目录
+        """
+        outputAnalysisDir = r"{}/{}".format(
+            self.confData.output_path, self.typeAnalyst())
+        dir.create_directory(outputAnalysisDir)
+
+        return outputAnalysisDir
+    
+    def analysisOfTurbine(self,
+                          dataFrame: pd.DataFrame,
+                          outputAnalysisDir,
+                          outputFilePath,
+                          confData: ConfBusiness,
+                          turbineName):
+        # dataFrame = self.filterCustom(dataFrame, confData)
+        self.turbineAnalysis(dataFrame, outputAnalysisDir,
+                             outputFilePath, confData, turbineName)
+
+    def turbineAnalysis(self,
+                        dataFrame: pd.DataFrame,
+                        outputAnalysisDir,
+                        outputFilePath,
+                        confData: ConfBusiness,
+                        turbineName):
+        pass
+    
+    def analysisOfTurbines(self, dataFrameMerge:pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
+        self.turbinesAnalysis(dataFrameMerge ,outputAnalysisDir,confData)
+
+    def turbinesAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
+        pass

+ 13 - 0
wtoaamapi/apps/business/algorithm/commonBusiness.py

@@ -0,0 +1,13 @@
+import pandas as pd
+from confBusiness import Field_GeneratorTorque,ConfBusiness
+
+
+class CommonBusiness:
+    def isNone(self, value):
+        return value is None
+
+    def recalculationOfGeneratorSpeedforShow(self,dataFrame:pd.DataFrame,confData:ConfBusiness):   
+        if not self.isNone(confData.value_gen_speed_multiple) and confData.field_gen_speed in dataFrame.columns:
+            dataFrame[confData.field_gen_speed] = dataFrame[confData.field_gen_speed] * \
+                confData.value_gen_speed_multiple
+            dataFrame[Field_GeneratorTorque] = dataFrame[confData.field_gen_speed]

+ 197 - 0
wtoaamapi/apps/business/algorithm/cpAnalyst.py

@@ -0,0 +1,197 @@
+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 .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
+
+
+class CpAnalyst(Analyst):
+    """
+    风电机组风能利用系数分析
+    """
+
+    def typeAnalyst(self):
+        return "cp"
+
+    def turbineAnalysis(self,
+                        dataFrame,
+                        outputAnalysisDir,
+                        outputFilePath,
+                        confData: ConfBusiness,
+                        turbineName):
+
+        self.cp(dataFrame, outputFilePath,
+                confData.field_wind_speed, confData.field_rotor_speed, confData.field_power, confData.field_pitch_angle1, confData.rotor_diameter, confData.density_air)
+
+    def cp(self, dataFrame, output_path, wind_speed_col, field_rotor_speed, power_col, pitch_col, rotor_diameter, air_density):
+        print('rotor_diameter={}  air_density={}'.format(
+            rotor_diameter, air_density))
+
+        dataFrame['power'] = dataFrame[power_col]  # Alias the power column
+        # Floor division by 10 and then multiply by 10
+        dataFrame['power_floor'] = (dataFrame[power_col] / 10).astype(int) * 10
+        dataFrame['wind_speed'] = dataFrame[wind_speed_col].astype(float)
+        dataFrame['rotor_speed'] = dataFrame[field_rotor_speed].astype(float)
+
+        # Power coefficient calculation
+        dataFrame['power'] = pd.to_numeric(dataFrame['power'], errors='coerce')
+        dataFrame['wind_speed'] = pd.to_numeric(
+            dataFrame['wind_speed'], errors='coerce')
+        rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce')
+        air_density = pd.to_numeric(air_density, errors='coerce')
+        print('rotor_diameter={}  air_density={}'.format(
+            rotor_diameter, air_density))
+        # Calculate cp
+        dataFrame['cp'] = dataFrame['power'] * 1000 / \
+            (0.5 * np.pi * air_density *
+             (rotor_diameter ** 2) / 4 * dataFrame['wind_speed'] ** 3)
+
+        # Group by power_floor and calculate mean, max, and min of the specified columns
+        grouped = dataFrame.groupby('power_floor').agg(
+            wind_speed=('wind_speed', 'mean'),
+            rotor_speed=('rotor_speed', 'mean'),
+            cp=('cp', 'mean'),
+            cp_max=('cp', 'max'),
+            cp_min=('cp', 'min'),
+        ).reset_index()
+
+        # grouped = dataFrame.groupby('power_floor').agg({
+        #     'wind_speed': 'mean',
+        #     'rotor_speed': 'mean',
+        #     'cp': ['mean', 'max', 'min']
+        # }).reset_index()
+
+        # Rename columns post aggregation for clarity
+        grouped.columns = ['power_floor', 'wind_speed',
+                           'rotor_speed', 'cp', 'cp_max', 'cp_min']
+
+        # Sort by power_floor
+        grouped = grouped.sort_values('power_floor')
+
+        # Write the dataframe to a CSV file
+        grouped.to_csv(output_path, index=False)
+
+    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
+        self.generate_cp_distribution(outputAnalysisDir, confData.farm_name)
+
+    def generate_cp_distribution(self, csvFileDirOfCp, farm_name, encoding='utf-8'):
+        """
+        Generates Cp distribution plots for turbines in a wind farm.
+
+        Parameters:
+        - csvFileDirOfCp: str, path to the directory containing input CSV files.
+        - farm_name: str, name of the wind farm.
+        - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
+        """
+
+        output_path = csvFileDirOfCp
+
+        field_Name_Turbine = "turbine_name"
+        x_name = 'power_floor'
+        y_name = 'cp'
+        split_way = '_cp.csv'
+
+        sns.set_palette('deep')
+        res = pd.DataFrame()
+
+        for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
+            for file_name in file_names:
+
+                if not file_name.endswith(".csv"):
+                    continue
+
+                file_path = os.path.join(root, file_name)
+                print(file_path)
+                frame = pd.read_csv(file_path, encoding=encoding)
+                turbine_name = file_name.split(split_way)[0]
+                frame[field_Name_Turbine] = turbine_name
+
+                res = pd.concat(
+                    [res, frame.loc[:, [field_Name_Turbine, x_name, y_name]]], axis=0)
+
+        ress = res.reset_index()
+
+        fig, ax = plt.subplots(figsize=(16, 8))
+        ax = sns.lineplot(x=x_name, y=y_name, data=ress,
+                          hue=field_Name_Turbine)
+        ax.set_title('Cp-Distribution')
+        # plt.legend(ncol=4)
+        plt.legend(title='turbine',bbox_to_anchor=(1.02, 0.5),ncol=2, loc='center left', borderaxespad=0.) 
+        plt.savefig(os.path.join(
+            output_path, "{}-Cp-Distribution.png".format(farm_name)), bbox_inches='tight', dpi=300)
+        plt.close()
+
+        grouped = ress.groupby(field_Name_Turbine)
+        for name, group in grouped:
+            color = ["lightgrey"] * len(ress[field_Name_Turbine].unique())
+            fig, ax = plt.subplots(figsize=(8, 8))
+            ax = sns.lineplot(x=x_name, y=y_name, data=ress, hue=field_Name_Turbine,
+                              palette=sns.color_palette(color), legend=False)
+            ax = sns.lineplot(x=x_name, y=y_name, data=group,
+                              color='darkblue', legend=False)
+            ax.set_title('turbine name={}'.format(name))
+            plt.savefig(os.path.join(output_path, "{}.png".format(
+                name)), bbox_inches='tight', dpi=120)
+            plt.close()
+
+    def plot_cp_distribution(self, csvFileDir,  farm_name):
+        field_Name_Turbine = "设备名"
+        x_name = 'power_floor'
+        y_name = 'cp'
+        split_way = '_cp.csv'
+        # Create the output path based on the farm name
+        output_path = csvFileDir  # output_path_template.format(farm_name)
+        # Ensure the output directory exists
+        os.makedirs(output_path, exist_ok=True)
+        print(csvFileDir)
+        # Initialize a DataFrame to store results
+        res = pd.DataFrame()
+
+        # Walk through the input directory to process each file
+        for root, _, file_names in dir.list_directory(csvFileDir):
+            for file_name in file_names:
+                full_path = os.path.join(root, file_name)
+                frame = pd.read_csv(full_path, encoding='gbk')
+                turbine_name = file_name.split(split_way)[0]
+                print("turbine_name={}".format(turbine_name))
+                frame[field_Name_Turbine] = turbine_name
+                res = pd.concat(
+                    [res, frame.loc[:, [field_Name_Turbine, x_name, y_name]]], axis=0)
+
+        # Reset index for plotting
+        ress = res.reset_index(drop=True)
+
+        # Plot combined Cp distribution for all turbines
+        fig = make_subplots(rows=1, cols=1)
+        for name, group in ress.groupby(field_Name_Turbine):
+            fig.add_trace(go.Scatter(
+                x=group[x_name], y=group[y_name], mode='lines', name=name))
+
+        fig.update_layout(title_text='{} Cp分布'.format(
+            farm_name), xaxis_title=x_name, yaxis_title=y_name)
+        fig.write_image(os.path.join(
+            output_path, "{}Cp分布.png".format(farm_name)), scale=3)
+
+        # Plot individual Cp distributions
+        unique_turbines = ress[field_Name_Turbine].unique()
+        for name in unique_turbines:
+            individual_fig = make_subplots(rows=1, cols=1)
+            # Add all turbines in grey
+            for turbine in unique_turbines:
+                group = ress[ress[field_Name_Turbine] == turbine]
+                individual_fig.add_trace(go.Scatter(
+                    x=group[x_name], y=group[y_name], mode='lines', name=turbine, line=dict(color='lightgrey')))
+
+            # Highlight the current turbine in dark blue
+            group = ress[ress[field_Name_Turbine] == name]
+            individual_fig.add_trace(go.Scatter(
+                x=group[x_name], y=group[y_name], mode='lines', name=name, line=dict(color='darkblue')))
+
+            individual_fig.update_layout(title_text='设备名={}'.format(name))
+            individual_fig.write_image(os.path.join(
+                output_path, "all-{}.png".format(name)), scale=2)

+ 116 - 0
wtoaamapi/apps/business/algorithm/cpTrendAnalyst.py

@@ -0,0 +1,116 @@
+import os
+import pandas as pd
+import numpy as np
+from plotly.subplots import make_subplots
+import plotly.graph_objects as go
+import matplotlib.pyplot as plt
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
+
+
+class CpTrendAnalyst(Analyst):
+    """
+    风电机组风能利用系数时序分析
+    """
+
+    def typeAnalyst(self):
+        return "cp_trend"
+
+    def turbineAnalysis(self,
+                        dataFrame,
+                        outputAnalysisDir,
+                        outputFilePath,
+                        confData: ConfBusiness,
+                        turbineName):
+
+        self.cp_trend(dataFrame, outputFilePath,
+                      confData.field_turbine_time, confData.field_wind_speed, confData.field_rotor_speed, confData.field_power, confData.field_pitch_angle1,
+                      confData.rotor_diameter, confData.density_air)
+
+    def cp_trend(self, dataFrame, outputFilePath,  time_col, wind_speed_col, generator_speed_col, power_col, pitch_col, rotor_diameter, density_air):
+        dataFrame['time_day'] = dataFrame[time_col].dt.date
+
+        # Assign columns and calculate 'cp'
+        dataFrame['wind_speed'] = dataFrame[wind_speed_col].astype(float)
+        dataFrame['rotor_speed'] = dataFrame[generator_speed_col].astype(float)
+        dataFrame['power'] = dataFrame[power_col]
+
+        # Power coefficient calculation
+        rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce')
+        air_density = pd.to_numeric(rotor_diameter, errors='coerce')
+        # Calculate cp
+        dataFrame['cp'] = dataFrame['power'] * 1000 / (0.5 * np.pi * air_density * (
+            rotor_diameter ** 2) / 4 * dataFrame['wind_speed'] ** 3)
+
+        # Group by day and aggregate
+        grouped = dataFrame.groupby('time_day').agg({
+            time_col: 'min',  # Assuming time_col is the datetime column for minimum time
+            'wind_speed': 'mean',
+            'rotor_speed': 'mean',
+            'cp': ['mean', 'max', 'min']
+        }).reset_index()
+
+        # Rename columns post aggregation for clarity
+        grouped.columns = ['time_day', 'time_', 'wind_speed',
+                           'rotor_speed', 'cp', 'cp_max', 'cp_min']
+
+        # Sort by day
+        grouped = grouped.sort_values('time_day')
+
+        # Write to CSV
+        grouped.to_csv(outputFilePath, index=False)
+
+    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
+        self.create_cp_trend_plots(outputAnalysisDir, confData.farm_name)
+
+    def create_cp_trend_plots(self, csvFileDirOfCp,  farm_name, encoding='utf-8'):
+        """
+        Generates and saves error bar plots for CP trend data stored in CSV files.
+
+        Parameters:
+        - csvFileDirOfCp: Path to the directory containing the input CSV files.
+        - farm_name: Name of the farm, used to format the output path.
+        - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
+        """
+        time_day = 'time_day'
+        y_name = 'cp'
+        y_min = 'cp_min'
+        y_max = 'cp_max'
+        split_way = '_cp_trend.csv'
+
+        # Create the output directory if it does not exist
+        if not os.path.exists(csvFileDirOfCp):
+            os.makedirs(csvFileDirOfCp)
+
+        # Walk through the input directory to process each CSV file
+        for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
+            for file_name in file_names:
+                if not file_name.endswith(".csv"):
+                    continue
+
+                # Read each CSV file
+                data = pd.read_csv(os.path.join(
+                    root, file_name), encoding=encoding)
+                data.loc[:, time_day] = pd.to_datetime(data.loc[:, time_day])
+                data[y_min] = data[y_name] - data[y_min]
+                data[y_max] = data[y_max] - data[y_name]
+                turbine_name = file_name.split(split_way)[0]
+
+                # Generate the plot
+                fig, ax = plt.subplots()
+                ax.errorbar(x=data[time_day], y=data[y_name], yerr=[data[y_min], data[y_max]],
+                            fmt='o', capsize=4, elinewidth=2, ecolor='lightgrey', mfc='dodgerblue')
+
+                ax.set_xlabel('time')
+                ax.set_ylabel('Cp')
+                # ax.set_ylim(-0.2, 8)
+                ax.set_title('{}={}'.format('turbine_name', turbine_name))
+                # 旋转x轴刻度标签
+                plt.xticks(rotation=45)
+
+
+                # Save the plot
+                plt.savefig(os.path.join(csvFileDirOfCp, "{}.png".format(
+                    turbine_name)), bbox_inches='tight', dpi=120)
+                plt.close()

+ 22 - 41
dataAnalysisBusiness/algorithm/cpWindSpeedAnalyst.py → wtoaamapi/apps/business/algorithm/cpWindSpeedAnalyst.py

@@ -5,13 +5,12 @@ 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 behavior.analystExcludeRatedPower import AnalystExcludeRatedPower
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
 
 
-class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
+class CpWindSpeedAnalyst(Analyst):
     """
     风电机组风能利用系数分析
     """
@@ -33,12 +32,9 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         print('rotor_diameter={}  air_density={}'.format(
             rotor_diameter, air_density))
 
-        dataFrame = dataFrame.dropna(subset=[power_col])
-
         dataFrame['power'] = dataFrame[power_col]  # Alias the power column
         # Floor division by 10 and then multiply by 10
-        dataFrame['wind_speed_floor'] = (
-            dataFrame[wind_speed_col] / 1).astype(int) + 0.5
+        dataFrame['wind_speed_floor'] = (dataFrame[wind_speed_col] / 1).astype(int) + 0.5
         dataFrame['wind_speed'] = dataFrame[wind_speed_col].astype(float)
         dataFrame['rotor_speed'] = dataFrame[field_rotor_speed].astype(float)
 
@@ -46,13 +42,14 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         dataFrame['power'] = pd.to_numeric(dataFrame['power'], errors='coerce')
         dataFrame['wind_speed'] = pd.to_numeric(
             dataFrame['wind_speed'], errors='coerce')
-        # rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce')
-        # air_density = pd.to_numeric(air_density, errors='coerce')
-
-        # # Calculate cp
-        # dataFrame['cp'] = dataFrame['power'] * 1000 / \
-        #     (0.5 * np.pi * air_density *
-        #      (rotor_diameter ** 2) / 4 * dataFrame['wind_speed'] ** 3)
+        rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce')
+        air_density = pd.to_numeric(air_density, errors='coerce')
+        print('rotor_diameter={}  air_density={}'.format(
+            rotor_diameter, air_density))
+        # Calculate cp
+        dataFrame['cp'] = dataFrame['power'] * 1000 / \
+            (0.5 * np.pi * air_density *
+             (rotor_diameter ** 2) / 4 * dataFrame['wind_speed'] ** 3)
 
         # Group by wind_speed_floor and calculate mean, max, and min of the specified columns
         grouped = dataFrame.groupby('wind_speed_floor').agg(
@@ -74,9 +71,9 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         grouped.to_csv(output_path, index=False)
 
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.generate_cp_distribution(outputAnalysisDir, confData)
+        self.generate_cp_distribution(outputAnalysisDir, confData.farm_name)
 
-    def generate_cp_distribution(self, csvFileDirOfCp, confData: ConfBusiness, encoding='utf-8'):
+    def generate_cp_distribution(self, csvFileDirOfCp, farm_name, encoding='utf-8'):
         """
         Generates Cp distribution plots for turbines in a wind farm.
 
@@ -87,11 +84,11 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         """
 
         output_path = csvFileDirOfCp
-        value_step = 0.5
 
         field_Name_Turbine = "turbine_name"
         x_name = 'wind_speed_floor'
         y_name = 'cp'
+        split_way = '_cp_windspeed.csv'
 
         sns.set_palette('deep')
         res = pd.DataFrame()
@@ -99,13 +96,13 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
             for file_name in file_names:
 
-                if not file_name.endswith(CSVSuffix):
+                if not file_name.endswith(".csv"):
                     continue
 
                 file_path = os.path.join(root, file_name)
                 print(file_path)
                 frame = pd.read_csv(file_path, encoding=encoding)
-                turbine_name = file_name.split(CSVSuffix)[0]
+                turbine_name = file_name.split(split_way)[0]
                 frame[field_Name_Turbine] = turbine_name
 
                 res = pd.concat(
@@ -113,21 +110,13 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
 
         ress = res.reset_index()
 
-        fig, ax = plt.subplots()
+        fig, ax = plt.subplots(figsize=(16, 8))
         ax = sns.lineplot(x=x_name, y=y_name, data=ress,
                           hue=field_Name_Turbine)
-
-        ax.xaxis.set_major_locator(MultipleLocator(1))  # 创建一个刻度 ,将定位器应用到y轴上
-        ax.set_xlim(0, 26)
-        ax.yaxis.set_major_locator(MultipleLocator(
-            confData.graphSets["cp"]["step"] if not self.common.isNone(confData.graphSets["cp"]["step"]) else value_step))  # 创建一个刻度 ,将定位器应用到y轴上
-        ax.set_ylim(confData.graphSets["cp"]["min"] if not self.common.isNone(confData.graphSets["cp"]["min"])
-                    else 0, confData.graphSets["cp"]["max"] if not self.common.isNone(confData.graphSets["cp"]["max"]) else 1)
         ax.set_title('Cp-Distribution')
-        plt.xticks(rotation=45)  # 旋转45度
         plt.legend(ncol=4)
         plt.savefig(os.path.join(
-            output_path, "{}-Cp-Distribution.png".format(confData.farm_name)), bbox_inches='tight', dpi=300)
+            output_path, "{}-Cp-Distribution.png".format(farm_name)), bbox_inches='tight', dpi=300)
         plt.close()
 
         grouped = ress.groupby(field_Name_Turbine)
@@ -138,16 +127,7 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
                               palette=sns.color_palette(color), legend=False)
             ax = sns.lineplot(x=x_name, y=y_name, data=group,
                               color='darkblue', legend=False)
-
-            ax.xaxis.set_major_locator(
-                MultipleLocator(1))  # 创建一个刻度 ,将定位器应用到y轴上
-            ax.set_xlim(0, 26)
-            ax.yaxis.set_major_locator(MultipleLocator(
-                confData.graphSets["cp"]["step"] if not self.common.isNone(confData.graphSets["cp"]["step"]) else value_step))  # 创建一个刻度 ,将定位器应用到y轴上
-            ax.set_ylim(confData.graphSets["cp"]["min"] if not self.common.isNone(confData.graphSets["cp"]["min"])
-                        else 0, confData.graphSets["cp"]["max"] if not self.common.isNone(confData.graphSets["cp"]["max"]) else 1)
             ax.set_title('turbine name={}'.format(name))
-            plt.xticks(rotation=45)  # 旋转45度
             plt.savefig(os.path.join(output_path, "{}.png".format(
                 name)), bbox_inches='tight', dpi=120)
             plt.close()
@@ -156,6 +136,7 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         field_Name_Turbine = "设备名"
         x_name = 'wind_speed_floor'
         y_name = 'cp'
+        split_way = '_cp.csv'
         # Create the output path based on the farm name
         output_path = csvFileDir  # output_path_template.format(farm_name)
         # Ensure the output directory exists
@@ -169,7 +150,7 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
             for file_name in file_names:
                 full_path = os.path.join(root, file_name)
                 frame = pd.read_csv(full_path, encoding='gbk')
-                turbine_name = file_name.split(CSVSuffix)[0]
+                turbine_name = file_name.split(split_way)[0]
                 print("turbine_name={}".format(turbine_name))
                 frame[field_Name_Turbine] = turbine_name
                 res = pd.concat(

+ 2 - 2
dataAnalysisBusiness/algorithm/dataIntegrityOfMinuteAnalyst.py → wtoaamapi/apps/business/algorithm/dataIntegrityOfMinuteAnalyst.py

@@ -7,8 +7,8 @@ import seaborn as sns
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness,charset_unify
 from .dataIntegrityOfSecondAnalyst import DataIntegrityOfSecondAnalyst
 
 

+ 15 - 18
dataAnalysisBusiness/algorithm/dataIntegrityOfSecondAnalyst.py → wtoaamapi/apps/business/algorithm/dataIntegrityOfSecondAnalyst.py

@@ -7,23 +7,20 @@ import seaborn as sns
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .baseAnalyst import BaseAnalyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import *
 import calendar
 import random 
 
 
-class DataIntegrityOfSecondAnalyst(Analyst):
+class DataIntegrityOfSecondAnalyst(BaseAnalyst):
     """
     风电机组秒级数据完整度分析
     """
 
     def typeAnalyst(self):
         return "data_integrity_second"
-    
-    def filterCommon(self,dataFrame:pd.DataFrame, confData:ConfBusiness):        
-        return dataFrame
 
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
         groupedDataFrame = self.dataIntegrityByMonth(
@@ -42,16 +39,16 @@ class DataIntegrityOfSecondAnalyst(Analyst):
     def fullMonthIndex(self,start_time,end_time,turbine_name,new_frame):
         months = (end_time.year - start_time.year)*12 + end_time.month - start_time.month
         month_range = ['%04d-%02d' % (int(start_time.year + mon//12), int(mon%12+1)) for mon in range(start_time.month-1, start_time.month+months)]
-        month_index = pd.DataFrame(month_range,columns=[Field_YearMonth])
+        month_index = pd.DataFrame(month_range,columns=['year-month'])
 
         plot_res = pd.DataFrame()
         grouped = new_frame.groupby(turbine_name)
         for name,group in grouped:
-            group = pd.merge(group,month_index,on=Field_YearMonth,how='outer')
+            group = pd.merge(group,month_index,on='year-month',how='outer')
             group['数据完整度%'] = group['数据完整度%'].fillna(0)
             group[turbine_name] = name
-            group['year'] = group[Field_YearMonth].apply(lambda x:str(x).split('-')[0])
-            group['month'] = group[Field_YearMonth].apply(lambda x:str(x).split('-')[1])
+            group['year'] = group['year-month'].apply(lambda x:str(x).split('-')[0])
+            group['month'] = group['year-month'].apply(lambda x:str(x).split('-')[1])
             plot_res = pd.concat([plot_res,group],axis=0,sort=False)
 
         return plot_res
@@ -72,7 +69,7 @@ class DataIntegrityOfSecondAnalyst(Analyst):
         new_frame = new_frame.reset_index()
         new_frame['month'] = new_frame['month'].astype(
             str).apply(lambda x: x.zfill(2))
-        new_frame[Field_YearMonth] = new_frame['year'].astype(
+        new_frame['year-month'] = new_frame['year'].astype(
             str) + '-' + new_frame['month'].astype(str)
         
         new_frame = self.fullMonthIndex(confData.start_time,confData.end_time,fieldTurbineName,new_frame)
@@ -83,9 +80,9 @@ class DataIntegrityOfSecondAnalyst(Analyst):
         title = 'time integrity check(%)'
         fig, ax = plt.subplots(figsize=(18, 15), dpi=300)
         # 风机数量小于月份
-        if len(set(groupedDataFrame.loc[:, Field_YearMonth])) > len(set(groupedDataFrame.loc[:, fieldTurbineName])):
+        if len(set(groupedDataFrame.loc[:, 'year-month'])) > len(set(groupedDataFrame.loc[:, fieldTurbineName])):
             result = pd.pivot(groupedDataFrame, index=fieldTurbineName,
-                              columns=Field_YearMonth, values="数据完整度%")
+                              columns='year-month', values="数据完整度%")
             ax = sns.heatmap(data=result, square=True, annot=True,
                              linewidths=0.3, cbar=False, fmt='g',)
             bottom, top = ax.get_ylim()
@@ -97,7 +94,7 @@ class DataIntegrityOfSecondAnalyst(Analyst):
                         r'/{}数据完整度分析.png'.format(farmName), bbox_inches='tight')
             plt.close()
         else:
-            result = pd.pivot(groupedDataFrame, index=Field_YearMonth,
+            result = pd.pivot(groupedDataFrame, index='year-month',
                               columns=fieldTurbineName, values="数据完整度%")
             ax = sns.heatmap(data=result, square=True, annot=True,
                              linewidths=0.3, cbar=False, fmt='g',)
@@ -114,9 +111,9 @@ class DataIntegrityOfSecondAnalyst(Analyst):
 
     def draw(self, groupedDataFrame, outputAnalysisDir, farmName, fieldTurbineName):
         fig = make_subplots(rows=1, cols=1)
-        if len(set(groupedDataFrame[Field_YearMonth])) > len(set(groupedDataFrame[fieldTurbineName])):
+        if len(set(groupedDataFrame['year-month'])) > len(set(groupedDataFrame[fieldTurbineName])):
             result = groupedDataFrame.pivot(
-                index=fieldTurbineName, columns=Field_YearMonth, values="数据完整度%")
+                index=fieldTurbineName, columns='year-month', values="数据完整度%")
             fig.add_trace(
                 go.Heatmap(
                     z=result.values,
@@ -136,7 +133,7 @@ class DataIntegrityOfSecondAnalyst(Analyst):
             )
         else:
             result = groupedDataFrame.pivot(
-                index=Field_YearMonth, columns=fieldTurbineName, values="数据完整度%")
+                index='year-month', columns=fieldTurbineName, values="数据完整度%")
             fig.add_trace(
                 go.Heatmap(
                     z=result.values,

+ 444 - 0
wtoaamapi/apps/business/algorithm/dataProcessor.py

@@ -0,0 +1,444 @@
+import os
+from datetime import datetime
+import concurrent.futures
+import pandas as pd
+from confBusiness import ConfBusiness, Field_NameOfTurbine, Field_GeneratorSpeed, Field_GeneratorTorque,Field_AngleIncluded
+from .utils.directoryUtil import DirectoryUtil as dir
+from .baseAnalyst import BaseAnalyst
+from .analyst import Analyst
+from .commonBusiness import CommonBusiness
+
+class DataProcessor:
+    def __init__(self):
+        self.common=CommonBusiness()
+        self._baseAnalysts = []
+        self._noCustomFilterAnalysts = []
+        self._analysts = []
+        self.node_filter_value_state_turbine = "filter_value_state_turbine"
+        self.node_angle_pitch_min = "angle_pitch_min"
+        self.node_angle_pitch_max = "angle_pitch_max"
+        self.node_speed_wind_cut_in = "speed_wind_cut_in"
+        self.node_speed_wind_cut_out = "speed_wind_cut_out"
+        self.node_active_power_min = "active_power_min"
+        self.node_active_power_max = "active_power_max"
+
+    def attachBaseAnalyst(self, analyst: BaseAnalyst):
+        if analyst not in self._analysts:
+            self._baseAnalysts.append(analyst)
+
+    def detachBaseAnalyst(self, analyst: BaseAnalyst):
+        try:
+            self._baseAnalysts.remove(analyst)
+        except ValueError:
+            pass
+
+    def attach(self, analyst: Analyst):
+        if analyst not in self._analysts:
+            self._analysts.append(analyst)
+
+    def detach(self, analyst: Analyst):
+        try:
+            self._analysts.remove(analyst)
+        except ValueError:
+            pass
+
+    def turbineNotify(self,
+                      dataFrameOfTurbine: pd.DataFrame,
+                      confData: ConfBusiness,
+                      turbineName):
+        for analyst in self._analysts:
+            outputAnalysisDir = analyst.getOutputAnalysisDir()
+
+            outputFilePath = r"{}/{}_{}.csv".format(
+                outputAnalysisDir, turbineName, analyst.typeAnalyst())
+
+            analyst.analysisOfTurbine(
+                dataFrameOfTurbine, outputAnalysisDir, outputFilePath, confData, turbineName)
+
+    def turbinesNotify(self, dataFrameOfTurbines: pd.DataFrame,  confData: ConfBusiness):
+        for analyst in self._analysts:
+            outputAnalysisDir = analyst.getOutputAnalysisDir()
+            analyst.analysisOfTurbines(
+                dataFrameOfTurbines, outputAnalysisDir, confData)
+
+    def baseAnalystTurbineNotify(self,
+                                 dataFrameOfTurbine: pd.DataFrame,
+                                 confData: ConfBusiness,
+                                 turbineName):
+        for analyst in self._baseAnalysts:
+            outputAnalysisDir = analyst.getOutputAnalysisDir()
+
+            outputFilePath = r"{}/{}_{}.csv".format(
+                outputAnalysisDir, turbineName, analyst.typeAnalyst())
+
+            analyst.analysisOfTurbine(
+                dataFrameOfTurbine, outputAnalysisDir, outputFilePath, confData, turbineName)
+
+    def baseAnalystNotify(self, dataFrameOfTurbines: pd.DataFrame,  confData: ConfBusiness):
+        for analyst in self._baseAnalysts:
+            outputAnalysisDir = analyst.getOutputAnalysisDir()
+            analyst.analysisOfTurbines(
+                dataFrameOfTurbines, outputAnalysisDir, confData)
+
+    def filterCustom(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
+        if not self.common.isNone(confData.field_wind_speed) and self.node_speed_wind_cut_in in confData.filter \
+                and not self.common.isNone(confData.filter[self.node_speed_wind_cut_in]) \
+                and not self.common.isNone(confData.field_wind_speed) and self.node_speed_wind_cut_out in confData.filter \
+                and not self.common.isNone(confData.filter[self.node_speed_wind_cut_out]):
+            windSpeedCutIn = float(
+                confData.filter[self.node_speed_wind_cut_in])
+            windSpeedCutOut = float(
+                confData.filter[self.node_speed_wind_cut_out])
+
+            dataFrame = dataFrame[~((dataFrame[confData.field_wind_speed] > windSpeedCutOut) | (
+                dataFrame[confData.field_wind_speed] < 0))]
+            dataFrame = dataFrame[~((dataFrame[confData.field_wind_speed] < windSpeedCutIn) & (
+                dataFrame[confData.field_power] < confData.rated_power*1.2))]
+
+            dataFrame = dataFrame[(
+                dataFrame[confData.field_power] <= confData.rated_power*1.2)]
+
+        # Filter rows where turbine state
+        if not self.common.isNone(confData.field_turbine_state) and self.node_filter_value_state_turbine in confData.filter and not self.common.isNone(confData.filter[self.node_filter_value_state_turbine]):
+            stateTurbine = confData.filter[self.node_filter_value_state_turbine]
+            dataFrame = dataFrame[dataFrame[confData.field_turbine_state].isin(
+                stateTurbine)]
+
+        # # Filter rows where pitch
+        # if 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]):
+        #     anglePitchMin = float(confData.filter[self.node_angle_pitch_min])
+        #     dataFrame = dataFrame[(
+        #         dataFrame[confData.field_pitch_angle1] >= anglePitchMin)]
+
+        # if not self.common.isNone(confData.field_pitch_angle1) and self.node_angle_pitch_max in confData.filter and not self.common.isNone(confData.filter[self.node_angle_pitch_max]):
+        #     anglePitchMax = float(confData.filter[self.node_angle_pitch_max])
+        #     dataFrame = dataFrame[(
+        #         dataFrame[confData.field_pitch_angle1] <= anglePitchMax)]
+
+        # # Filter rows where wind speed
+        # if not self.common.isNone(confData.field_wind_speed) and self.node_speed_wind_cut_in in confData.filter and not self.common.isNone(confData.filter[self.node_speed_wind_cut_in]):
+        #     windSpeedCutIn = float(confData.filter[self.node_speed_wind_cut_in])
+        #     dataFrame = dataFrame[(
+        #         dataFrame[confData.field_wind_speed] >= windSpeedCutIn)]
+
+        # if not self.common.isNone(confData.field_wind_speed) and self.node_speed_wind_cut_out in confData.filter and not self.common.isNone(confData.filter[self.node_speed_wind_cut_out]):
+        #     windSpeedCutOut = float(confData.filter[self.node_speed_wind_cut_out])
+        #     dataFrame = dataFrame[(
+        #         dataFrame[confData.field_wind_speed] < windSpeedCutOut)]
+
+        # # Filter rows where power
+        # if not self.common.isNone(confData.field_power) and self.node_active_power_min in confData.filter and not self.common.isNone(confData.filter[self.node_active_power_min]):
+        #     activePowerMin = float(confData.filter[self.node_active_power_min])
+        #     dataFrame = dataFrame[(
+        #         dataFrame[confData.field_power] >= activePowerMin)]
+
+        # 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]):
+        #     activePowerMax = float(confData.filter[self.node_active_power_max])
+        #     dataFrame = dataFrame[(
+        #         dataFrame[confData.field_power] < activePowerMax)]
+
+        return dataFrame
+
+    def filterData(self, dataFrame: pd.DataFrame,  confData: ConfBusiness, dataFilter, rotationalSpeedRatio):
+        # dataFrame = dataFrame.dropna(axis=0,subset=[confData.field_power,confData.field_wind_speed,confData.field_pitch_angle1])
+        dataFrame = dataFrame.dropna(
+            axis=0, subset=self.getUseColumns(confData))        
+        
+        if confData.field_wind_speed in dataFrame.columns:
+            dataFrame[confData.field_wind_speed] = dataFrame[confData.field_wind_speed].astype('float32') 
+
+        if confData.field_wind_dir in dataFrame.columns:
+            dataFrame[confData.field_wind_dir]=dataFrame[confData.field_wind_dir].astype('float32') 
+
+        if confData.field_angle_included in dataFrame.columns:
+            dataFrame[confData.field_angle_included]=dataFrame[confData.field_angle_included].astype('float32') 
+
+        if confData.field_power in dataFrame.columns:
+            dataFrame[confData.field_power]=dataFrame[confData.field_power].astype('float32') 
+        
+        if confData.field_wind_dir in dataFrame.columns:
+            dataFrame[confData.field_wind_dir]=dataFrame[confData.field_wind_dir].astype('float32') 
+
+        if confData.field_pitch_angle1 in dataFrame.columns:
+            dataFrame[confData.field_pitch_angle1]=dataFrame[confData.field_pitch_angle1].astype('float32')
+
+        if confData.field_gen_speed in dataFrame.columns: 
+            dataFrame[confData.field_gen_speed]=dataFrame[confData.field_gen_speed].astype('float32') 
+        
+        if confData.field_rotor_speed in dataFrame.columns:
+            dataFrame[confData.field_rotor_speed]=dataFrame[confData.field_rotor_speed].astype('float32') 
+
+        dataFrame = dataFrame[(dataFrame[confData.field_turbine_time] >= confData.start_time) & (
+            dataFrame[confData.field_turbine_time] < confData.end_time)]
+
+        if not self.common.isNone(confData.excludingMonths) and len(confData.excludingMonths) > 0:
+            # 给定的日期列表
+            date_strings = []
+            for month in confData.excludingMonths:
+                if not self.common.isNone(month):
+                    date_strings.append(month)
+
+            if len(date_strings) > 0:
+                mask = ~dataFrame["year-month"].isin(date_strings)
+
+                # 使用掩码过滤DataFrame,删除指定日期的行
+                dataFrame = dataFrame[mask]
+
+        dataFrame["年月"] = pd.to_datetime(
+            dataFrame[confData.field_turbine_time], format="%Y-%m")
+        dataFrame['日期'] = pd.to_datetime(
+            dataFrame[confData.field_turbine_time], format="%Y-%m")
+        dataFrame['monthIntTime'] = dataFrame['日期'].apply(
+            lambda x: x.timestamp())
+
+        dataFrame["year-month"] = dataFrame[confData.field_turbine_time].dt.strftime(
+            '%Y-%m')
+
+        return dataFrame
+
+    def calculateAngleIncluded(self, array1, array2):
+        """
+        计算两个相同长度角度数组中两两对应角度值的偏差。
+        结果限制在-90°到+90°之间,并保留两位小数。
+
+        参数:
+        array1 (list): 第一个角度数组
+        array2 (list): 第二个角度数组
+
+        返回:
+        list: 两两对应角度的偏差列表
+        """
+        deviations = []
+        for angle1, angle2 in zip(array1, array2):
+            # 计算原始偏差
+            deviation = angle1 - angle2
+
+            # 调整偏差,使其位于-180°到+180°范围内
+            if deviation == 0.0:
+                deviation = 0.0
+            else:
+                deviation = (deviation + 180) % 360 - 180
+
+            # 将偏差限制在-90°到+90°范围内
+            if deviation > 90:
+                deviation -= 180
+            elif deviation < -90:
+                deviation += 180
+
+            # 保留两位小数
+            deviations.append(round(deviation, 2))
+
+        return deviations
+
+    def recalculationOfIncludedAngle(self, dataFrame: pd.DataFrame, fieldAngleIncluded, fieldWindDirect, fieldNacellePos):
+        """
+        依据机舱位置(角度)、风向计算两者夹角
+        """
+        if fieldAngleIncluded not in dataFrame.columns and fieldWindDirect in dataFrame.columns and fieldNacellePos in dataFrame.columns:
+            dataFrame[Field_AngleIncluded] = self.calculateAngleIncluded()
+        else:
+            dataFrame[Field_AngleIncluded] = dataFrame[fieldAngleIncluded]
+
+    def recalculationOfGeneratorSpeed(self, dataFrame: pd.DataFrame, fieldRotorSpeed, fieldGeneratorSpeed, rotationalSpeedRatio):
+        """
+        风电机组发电机转速再计算,公式:转速比=发电机转速/叶轮或主轴转速
+        """
+        if fieldGeneratorSpeed in dataFrame.columns:            
+            dataFrame[Field_GeneratorSpeed] = dataFrame[fieldGeneratorSpeed]
+
+        if fieldGeneratorSpeed not in dataFrame.columns and fieldRotorSpeed in dataFrame.columns:
+            dataFrame[fieldGeneratorSpeed] = rotationalSpeedRatio * \
+                dataFrame[fieldRotorSpeed]
+
+    def recalculationOfRotorSpeed(self, dataFrame: pd.DataFrame, fieldRotorSpeed, fieldGeneratorSpeed, rotationalSpeedRatio):
+        """
+        风电机组发电机转速再计算,公式:转速比=发电机转速/叶轮或主轴转速
+        """
+        if fieldRotorSpeed not in dataFrame.columns and fieldGeneratorSpeed in dataFrame.columns:
+            dataFrame[fieldRotorSpeed] = dataFrame[fieldGeneratorSpeed] / \
+                rotationalSpeedRatio
+
+    def recalculationOfRotorTorque(self, dataFrame: pd.DataFrame, fieldGeneratorTorque, fieldActivePower, fieldGeneratorSpeed):
+        """
+        风电机组发电机转矩计算,P的单位换成KW转矩计算公式:
+        P*1000= pi/30*T*n  
+        30000/pi*P=T*n
+        30000/3.1415926*P=T*n
+        9549.297*p=T*n  
+        其中:n为发电机转速,p为有功功率,T为转矩
+        """
+        if fieldGeneratorTorque not in dataFrame.columns and fieldActivePower in dataFrame.columns and fieldGeneratorSpeed in dataFrame.columns:
+            dataFrame[fieldGeneratorTorque] = 9549.297 * \
+                dataFrame[fieldActivePower]/dataFrame[fieldGeneratorSpeed]
+
+        dataFrame[Field_GeneratorTorque] = dataFrame[fieldGeneratorTorque]
+        
+
+    def getUseColumns(self, confData: ConfBusiness):
+        useColumns = []
+
+        if not self.common.isNone(confData.field_turbine_time):
+            useColumns.append(confData.field_turbine_time)
+
+        if not self.common.isNone(confData.field_turbine_name):
+            useColumns.append(confData.field_turbine_name)
+
+        if not self.common.isNone(confData.field_wind_speed):
+            useColumns.append(confData.field_wind_speed)
+
+        if not self.common.isNone(confData.field_power):
+            useColumns.append(confData.field_power)
+
+        if not self.common.isNone(confData.field_pitch_angle1):
+            useColumns.append(confData.field_pitch_angle1)
+
+        if not self.common.isNone(confData.field_rotor_speed):
+            useColumns.append(confData.field_rotor_speed)
+
+        if not self.common.isNone(confData.field_gen_speed):
+            useColumns.append(confData.field_gen_speed)
+
+        if not self.common.isNone(confData.field_torque):
+            useColumns.append(confData.field_torque)
+
+        if not self.common.isNone(confData.field_wind_dir):
+            useColumns.append(confData.field_wind_dir)
+
+        if not self.common.isNone(confData.field_angle_included):
+            useColumns.append(confData.field_angle_included)
+
+        if not self.common.isNone(confData.field_nacelle_pos):
+            useColumns.append(confData.field_nacelle_pos)
+
+        if not self.common.isNone(confData.field_env_temp):
+            useColumns.append(confData.field_env_temp)
+
+        if not self.common.isNone(confData.field_nacelle_temp):
+            useColumns.append(confData.field_nacelle_temp)
+
+        if not self.common.isNone(confData.field_turbine_state):
+            useColumns.append(confData.field_turbine_state)
+
+        if not self.common.isNone(confData.field_temperature_large_components):
+            temperature_cols = confData.field_temperature_large_components.split(
+                ',')
+            for temperatureColumn in temperature_cols:
+                useColumns.append(temperatureColumn)
+        print(useColumns)
+        return useColumns
+
+    def loadData(self, csvFilePath, skip, confData: ConfBusiness, turbineName):
+        # Load the CSV, skipping the specified initial rows
+        dataFrame = pd.read_csv(csvFilePath, header=0,usecols=self.getUseColumns(
+            confData), skiprows=range(1, skip+1))
+
+        dataFrame[Field_NameOfTurbine] = confData.add_W_if_starts_with_digit(
+            turbineName)
+
+        # 选择所有的数值型列
+        dataFrame = dataFrame.convert_dtypes()
+        numeric_cols = dataFrame.select_dtypes(
+            include=['float64', 'float16']).columns
+
+        # 将这些列转换为float32
+        dataFrame[numeric_cols] = dataFrame[numeric_cols].astype('float32')
+
+        # 首先尝试去除字符串前后的空白
+        dataFrame[confData.field_turbine_time] = dataFrame[confData.field_turbine_time].str.strip()
+        dataFrame[confData.field_turbine_time] = pd.to_datetime(
+            dataFrame[confData.field_turbine_time],format='%Y-%m-%d %H:%M:%S',errors="coerce")
+        dataFrame[confData.field_turbine_time] = dataFrame[confData.field_turbine_time].dt.strftime(
+            '%Y-%m-%d %H:%M:%S')
+        dataFrame[confData.field_turbine_time] = pd.to_datetime(
+            dataFrame[confData.field_turbine_time])
+
+        # 对除了“时间”列之外的所有列进行自下而上的填充(先反转后填充)
+        # 注意:补植须要考虑业务合理性
+        # dataFrame = dataFrame.fillna(method='ffill')
+        # dataFrame = dataFrame.fillna(method='bfill')
+
+        return dataFrame
+
+    def execute(self, confData: ConfBusiness):
+        rotationalSpeedRatio = confData.rotational_Speed_Ratio  # 转速比
+        field_Rotor_Speed = confData.field_rotor_speed  # 字段 '主轴转速'
+        field_Generator_Speed = confData.field_gen_speed  # 字段 '发电机转速'
+        field_Torque = confData.field_torque  # 字段 "实际扭矩"
+        field_Active_Power = confData.field_power  # 字段 '有功功率'
+        dataFilter = confData.filter
+
+        # r'E:/BaiduNetdiskDownload/min_scada_TangZhen'
+        csvFileDir = confData.input_path
+        csvFileNameSplitStringForTurbine = confData.csvFileNameSplitStringForTurbine  # '.csv'
+        indexTurbine = confData.index_turbine
+        outputRootDir = confData.output_path  # r'output'
+
+        # Example usage:
+        outputDataAfterFilteringDir = r"{}/{}".format(
+            outputRootDir,  "DataAfterFiltering")
+        dir.create_directory(outputDataAfterFilteringDir)
+
+        dataFrameMergeFilter = pd.DataFrame()
+        for rootDir, subDirs, files in dir.list_directory(csvFileDir):
+            files = sorted(files)
+            for file in files:
+
+                if not file.endswith(".csv"):
+                    continue
+
+                csvFilePath = os.path.join(rootDir, file)
+                print(csvFilePath)
+
+                turbineName = confData.add_W_if_starts_with_digit(file.split(csvFileNameSplitStringForTurbine)[
+                    indexTurbine])
+
+                dataFrameFilter = self.loadData(
+                    csvFilePath, confData.skip_row_number, confData, turbineName)
+
+                dataFrameFilter = self.filterData(
+                    dataFrameFilter, confData, dataFilter, rotationalSpeedRatio)
+
+                self.baseAnalystTurbineNotify(dataFrameFilter,
+                                              confData,
+                                              turbineName)
+
+                dataFrameFilter = self.filterCustom(dataFrameFilter, confData)
+
+                dataFrameFilter = self.filterForMerge(
+                    dataFrameFilter, confData)
+
+                if len(dataFrameFilter) <= 0:
+                    print("dataFrameFilter not data.")
+                    continue
+
+                self.recalculationOfGeneratorSpeed(
+                    dataFrameFilter, field_Rotor_Speed, field_Generator_Speed, rotationalSpeedRatio)
+                self.recalculationOfRotorSpeed(
+                    dataFrameFilter, field_Rotor_Speed, field_Generator_Speed, rotationalSpeedRatio)
+                self.recalculationOfRotorTorque(
+                    dataFrameFilter, field_Torque, field_Active_Power, field_Generator_Speed)
+                self.recalculationOfIncludedAngle(
+                    dataFrameFilter, confData.field_angle_included, confData.field_wind_dir, confData.field_nacelle_pos)
+
+                dataFrameMergeFilter = pd.concat(
+                    [dataFrameMergeFilter, dataFrameFilter], axis=0, sort=False)
+
+                # dataFrameFilter.to_csv(os.path.join(
+                #     outputDataAfterFilteringDir, "{}.csv".format(turbineName)), index=False)
+                dataFrameFilter = self.turbineNotify(dataFrameFilter,
+                                                     confData,
+                                                     turbineName)
+
+        self.baseAnalystNotify(dataFrameMergeFilter,  confData)
+        self.turbinesNotify(dataFrameMergeFilter,  confData)
+
+    def filterForMerge(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
+        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_pitch_angle1] > anglePitchMin))]
+
+        return dataFrame

+ 4 - 4
dataAnalysisBusiness/algorithm/formula_cp.py → wtoaamapi/apps/business/algorithm/formula_cp.py

@@ -29,10 +29,10 @@ def calculate_cp(P, A, rho, v):
     return Cp
 
 # 示例变量
-diameter_example = 82 # 假设叶轮直径为46.2米
-P_example = 76.32*1000
-rho_example = 1.059
-v_example = 3.01
+diameter_example = 121  # 假设叶轮直径为46.2米
+P_example = 905*1000
+rho_example = 1.18
+v_example = 4.3380
 
 # 使用函数计算A
 A_example = calculate_area(diameter_example)

+ 92 - 0
wtoaamapi/apps/business/algorithm/generatorSpeedPowerAnalyst.py

@@ -0,0 +1,92 @@
+import os
+import pandas as pd
+from datetime import datetime
+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
+import matplotlib.cm as cm
+from matplotlib.ticker import MultipleLocator
+from matplotlib.colors import Normalize
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import Field_NameOfTurbine, ConfBusiness
+
+
+class GeneratorSpeedPowerAnalyst(Analyst):
+    """
+    风电机组发电机转速-有功功率分析
+    """
+
+    def typeAnalyst(self):
+        return "speed_power"
+
+    def turbinesAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
+        self.create_and_save_plots(
+            dataFrameMerge, outputAnalysisDir, confData, confData.field_gen_speed, confData.field_power)
+
+    def create_and_save_plots(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness, field_Generator_Speed, field_Active_Power):
+        x_name = 'generator_speed'
+        y_name = 'power'
+
+        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
+        for name, group in grouped:
+            # 创建图形和坐标轴
+            fig, ax = plt.subplots()
+            cmap = cm.get_cmap('rainbow')
+
+            # 绘制散点图
+            scatter = ax.scatter(x=group[confData.field_gen_speed]*confData.value_gen_speed_multiple if not self.common.isNone(confData.value_gen_speed_multiple) else group[confData.field_gen_speed],
+                                 y=group[confData.field_power], c=group['monthIntTime'], cmap=cmap, s=5)
+
+            # g = sns.lmplot(x=field_Generator_Speed, y=field_Active_Power, data=group,
+            #                fit_reg=False, scatter_kws={"s": 5, "color": "b"}, legend=False, height=6, aspect=1.2)
+            # g = sns.lmplot(x=field_Generator_Speed, y=field_Active_Power, data=group,
+            #                fit_reg=False, scatter_kws={"s": 5}, legend=False, height=6, aspect=1.2)
+            # for ax in g.axes.flat:
+            #     # ax.xaxis.set_major_locator(MultipleLocator(100))
+            #     ax.set_xlim(confData.value_gen_speed_min, confData.value_gen_speed_max)
+            #     ax.set_xlabel(x_name)
+            #     ax.set_ylabel(y_name)
+
+            
+
+            # 设置图形标题和坐标轴标签
+            ax.set_title(f'turbine_name={name}')
+            ax.set_xlim(confData.value_gen_speed_min,
+                        confData.value_gen_speed_max)
+            # 设置x轴的刻度步长  
+            # 假设您想要每100个单位一个刻度  
+            if not self.common.isNone(confData.value_gen_speed_step):
+                loc = MultipleLocator(confData.value_gen_speed_step) # 创建每100个单位一个刻度的定位器  
+                ax.xaxis.set_major_locator(loc) # 将定位器应用到x轴上 
+            ax.set_xlabel(x_name)
+            ax.set_ylabel(y_name)
+
+            # 显示图例,并调整位置
+            ax.legend(loc='lower right')
+
+            # 设置颜色条
+            unique_months = len(group['年月'].unique())
+            ticks = np.linspace(group['monthIntTime'].min(
+            ), group['monthIntTime'].max(), min(unique_months, 6))  # 减少刻度数量
+            ticklabels = [datetime.fromtimestamp(
+                tick).strftime('%Y-%m') for tick in ticks]
+            norm = Normalize(group['monthIntTime'].min(),
+                             group['monthIntTime'].max())
+            sm = cm.ScalarMappable(norm=norm, cmap=cmap)
+
+            # 添加颜色条
+            cbar = fig.colorbar(sm, ax=ax)
+            cbar.set_ticks(ticks)
+            cbar.set_ticklabels(ticklabels)
+            # 旋转x轴刻度标签
+            plt.xticks(rotation=45)
+
+            plt.tight_layout()
+            plt.title(f'{Field_NameOfTurbine}={name}')
+            # 保存图片到指定路径
+            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
+            plt.savefig(output_file, bbox_inches='tight', dpi=120)
+            plt.close()

+ 62 - 0
wtoaamapi/apps/business/algorithm/generatorSpeedTorqueAnalyst.py

@@ -0,0 +1,62 @@
+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 .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import Field_NameOfTurbine,Field_GeneratorTorque, ConfBusiness
+
+
+class GeneratorSpeedTorqueAnalyst(Analyst):
+    """
+    风电机组发电机转速-转矩分析
+    """
+
+    def typeAnalyst(self):
+        return "speed_torque"
+
+    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
+        self.create_and_save_plots(
+            dataFrameMerge, outputAnalysisDir, confData, confData.field_gen_speed, confData.field_power)
+
+    def create_and_save_plots(self, dataFrame: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness, fieldGeneratorSpeed, field_Active_Power):
+        x_name = 'generator_speed'
+        y_name = 'generator_torque'
+
+        grouped = dataFrame.groupby(Field_NameOfTurbine)
+        for name, group in grouped:
+            groupNew=group.copy()
+
+            if not self.common.isNone(confData.value_gen_speed_multiple):
+                groupNew[fieldGeneratorSpeed]=group[fieldGeneratorSpeed]*confData.value_gen_speed_multiple 
+            # sns.lmplot函数参数scatter_kws: 设置为{"s": 5}时,会出现颜色丢失问题;改为={"s": 5, "color": "b"}后,则造成图形风格不统一问题;
+            g = sns.lmplot(x=fieldGeneratorSpeed, y=Field_GeneratorTorque, data=groupNew, fit_reg=False, scatter_kws={
+                           "s": 5, "color": "b"}, legend=False, height=6, aspect=1.2)
+            # g = sns.lmplot(x=fieldGeneratorSpeed, y=Field_GeneratorTorque, data=group, fit_reg=False, scatter_kws={
+            #                "s": 5}, legend=False, height=6, aspect=1.2)
+            
+            for ax in g.axes.flat:
+                # ax.xaxis.set_major_locator(MultipleLocator(100))
+                ax.set_xlim(confData.value_gen_speed_min,
+                            confData.value_gen_speed_max)
+                
+                # 设置x轴的刻度步长  
+                # 假设您想要每100个单位一个刻度  
+                if not self.common.isNone(confData.value_gen_speed_step):
+                    loc = MultipleLocator(confData.value_gen_speed_step) # 创建每100个单位一个刻度的定位器  
+                    ax.xaxis.set_major_locator(loc) # 将定位器应用到x轴上 
+
+                ax.set_xlabel(x_name)
+                ax.set_ylabel(y_name)
+
+
+            plt.tight_layout()
+            plt.title(f'{Field_NameOfTurbine}={name}')
+            # 保存图片到指定路径
+            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
+            plt.savefig(output_file, bbox_inches='tight', dpi=120)
+            plt.close()

+ 122 - 0
wtoaamapi/apps/business/algorithm/minPitchAnalyst.py

@@ -0,0 +1,122 @@
+import os
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.cm as cm
+import math
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
+
+
+class MinPitchAnalyst(Analyst):
+    """
+    风电机组最小桨距角分析
+    """
+
+    def typeAnalyst(self):
+        return "min_pitch"
+
+    def turbineAnalysis(self,
+                        dataFrame,
+                        outputAnalysisDir,
+                        outputFilePath,
+                        confData: ConfBusiness,
+                        turbineName):
+        self.min_pitch(dataFrame, outputFilePath,
+                       confData.field_turbine_time, confData.field_pitch_angle1, confData.field_power)
+
+    def min_pitch(self, dataFrame,  output_path, time_col, pitch_col, power_col):
+        # Convert time column to datetime and extract date
+        dataFrame['date_'] = pd.to_datetime(
+            dataFrame[time_col], format='%Y-%m-%d %H:%M:%S').dt.date
+
+        # Convert pitch to float and calculate pitch floor
+        dataFrame['pitch'] = dataFrame[pitch_col].astype(float)
+        dataFrame['pitch_floor'] = dataFrame[pitch_col].astype(int)
+
+        # Group by date and pitch floor, then count occurrences
+        grouped = dataFrame.groupby(['date_', 'pitch_floor']
+                                    ).size().reset_index(name='count')
+
+        # Sort by date and pitch floor
+        grouped.sort_values(['date_', 'pitch_floor'], inplace=True)
+
+        # Write to CSV
+        grouped.to_csv(output_path, index=False)
+
+    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
+        self.create_and_save_plot(outputAnalysisDir, confData.farm_name)
+
+    def create_and_save_plot(self, csvFileDirOfCp, farm_name, encoding='utf-8'):
+        """
+        Generates Cp distribution plots for turbines in a wind farm.
+
+        Parameters:
+        - csvFileDirOfCp: str, path to the directory containing input CSV files.
+        - farm_name: str, name of the wind farm.
+        - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
+        """
+
+        field_Name_Turbine = "turbine_name"
+        file_time = 'date_'
+        pitch_name = 'pitch_floor'
+        count_name = 'count'
+        split_way = 'merged_min_pitch'
+        split_way = '_min_pitch.csv'
+
+        # 遍历输入路径下的所有文件
+        for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
+            for file_name in file_names:
+
+                if not file_name.endswith(".csv"):
+                    continue
+
+                print(os.path.join(root, file_name))
+                data = pd.read_csv(os.path.join(
+                    root, file_name), encoding=encoding)
+
+                # 将日期列转换为日期类型
+                data[file_time] = pd.to_datetime(
+                    data[file_time], format='%Y-%m-%d')
+
+                # 计算count的对数
+                data['log_of_count'] = data[count_name].apply(
+                    lambda x: math.log2(x))
+
+                # 获取输出文件名(不含split_way之后的部分)
+                turbine_name = file_name.split(split_way)[0]
+                # 添加设备名作为新列
+                data[field_Name_Turbine] = turbine_name
+
+                # 创建图表
+                fig, ax = plt.subplots(figsize=(12, 8), dpi=120)
+                cmap = cm.get_cmap('Blues')
+                norm = plt.Normalize(
+                    data['log_of_count'].min(), data['log_of_count'].max())
+
+                # 绘制散点图
+                ax.scatter(x=data[file_time], y=data[pitch_name],
+                           c=data['log_of_count'], cmap=cmap, s=5)
+
+                # 设置图表属性
+                ax.set_xlabel('time')
+                ax.set_ylabel('pitch angle')
+                ax.set_ylim(-2, 60)
+                ax.margins(x=0)
+
+                # 创建颜色条
+                sm = cm.ScalarMappable(norm=norm, cmap=cmap)
+                cbar = fig.colorbar(sm, ax=ax)
+                cbar.set_label('log2(count)')
+
+                # 设置图表标题
+                ax.set_title('turbine_name={}'.format(turbine_name))
+
+                # 保存图表
+                plot_path = os.path.join(
+                    csvFileDirOfCp, "{}.png".format(turbine_name))
+                plt.savefig(plot_path, bbox_inches='tight', dpi=120)
+
+                # 关闭图表,释放资源
+                plt.close(fig)

+ 15 - 19
dataAnalysisBusiness/algorithm/pitchGeneratorSpeedAnalyst.py → wtoaamapi/apps/business/algorithm/pitchGeneratorSpeedAnalyst.py

@@ -6,9 +6,9 @@ from plotly.subplots import make_subplots
 import seaborn as sns
 import matplotlib.pyplot as plt
 from matplotlib.ticker import MultipleLocator
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import *
 
 
 class PitchGeneratorSpeedAnalyst(Analyst):
@@ -31,11 +31,10 @@ class PitchGeneratorSpeedAnalyst(Analyst):
         sns.set_palette('deep')
         # 遍历每个设备并绘制散点图
         for name, group in grouped:
-            groupNew = group.copy()
+            groupNew=group.copy()
 
             if not self.common.isNone(confData.value_gen_speed_multiple):
-                groupNew[fieldGeneratorSpeed] = group[fieldGeneratorSpeed] * \
-                    confData.value_gen_speed_multiple
+                groupNew[fieldGeneratorSpeed]=group[fieldGeneratorSpeed]*confData.value_gen_speed_multiple 
 
             # sns.lmplot函数参数scatter_kws: 设置为{"s": 5}时,会出现颜色丢失问题;
             g = sns.lmplot(x=fieldGeneratorSpeed, y=fieldPitchAngle, data=groupNew,
@@ -45,24 +44,21 @@ class PitchGeneratorSpeedAnalyst(Analyst):
 
             # 设置x轴和y轴的刻度
             for ax in g.axes.flat:
-                ax.set_xlim(confData.graphSets["generatorSpeed"]["min"] if not self.common.isNone(
-                    confData.graphSets["generatorSpeed"]["min"]) else 1000, confData.graphSets["generatorSpeed"]["max"] if not self.common.isNone(confData.graphSets["generatorSpeed"]["max"]) else 2000)
-                # 设置x轴的刻度步长
-                # 假设您想要每100个单位一个刻度
-                loc = MultipleLocator(confData.graphSets["generatorSpeed"]["step"] if not self.common.isNone(
-                    confData.graphSets["generatorSpeed"]) and not self.common.isNone(
-                    confData.graphSets["generatorSpeed"]["step"]) else 200)
-                ax.xaxis.set_major_locator(loc)  # 将定位器应用到x轴上
+                # ax.xaxis.set_major_locator(MultipleLocator(100))
+                ax.set_xlim(confData.value_gen_speed_min, confData.value_gen_speed_max)
+                # 设置x轴的刻度步长  
+                # 假设您想要每100个单位一个刻度  
+                if not self.common.isNone(confData.value_gen_speed_step):
+                    loc = MultipleLocator(confData.value_gen_speed_step) # 创建每100个单位一个刻度的定位器  
+                    ax.xaxis.set_major_locator(loc) # 将定位器应用到x轴上 
 
-                ax.yaxis.set_major_locator(MultipleLocator(confData.graphSets["pitchAngle"]["step"] if not self.common.isNone(
-                    confData.graphSets["pitchAngle"]["step"]) else 2))
-                ax.set_ylim(confData.graphSets["pitchAngle"]["min"] if not self.common.isNone(
-                    confData.graphSets["pitchAngle"]["min"]) else -2, confData.graphSets["pitchAngle"]["max"] if not self.common.isNone(confData.graphSets["pitchAngle"]["max"]) else 28)
+                ax.yaxis.set_major_locator(MultipleLocator(2))
+                # ax.set_ylim(-1, 18)
 
                 ax.set_xlabel(x_name)
                 ax.set_ylabel(y_name)
 
-            # 设置x轴刻度值旋转角度为45度
+            # 设置x轴刻度值旋转角度为45度  
             plt.tick_params(axis='x', rotation=45)
             # 调整布局和设置标题
             plt.tight_layout()

+ 59 - 0
wtoaamapi/apps/business/algorithm/pitchPowerAnalyst.py

@@ -0,0 +1,59 @@
+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 .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import *
+
+
+class PitchPowerAnalyst(Analyst):
+    """
+    风电机组变桨-功率分析
+    """
+
+    def typeAnalyst(self):
+        return "pitch_power"
+
+    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
+        self.plot_power_pitch_angle(dataFrameMerge, outputAnalysisDir, confData)
+
+    def plot_power_pitch_angle(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
+        x_name = 'power'
+        y_name = 'pitch_angle'
+        # 按设备名分组数据
+        grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
+        print("self.ratedPower {}".format(confData.rated_power))
+        # 遍历每个设备并绘制散点图
+        for name, group in grouped:
+            # sns.lmplot函数参数scatter_kws: 设置为{"s": 5}时,会出现颜色丢失问题;
+            g = sns.lmplot(x=confData.field_power, y=confData.field_pitch_angle1, data=group,
+                           fit_reg=False, scatter_kws={"s": 5, "color": "b"}, legend=False, height=6, aspect=1.2)
+            # g = sns.lmplot(x=confData.field_power, y=confData.field_pitch_angle1, data=group,
+            #                fit_reg=False, scatter_kws={"s": 5}, legend=False, height=6, aspect=1.2)
+
+            # 设置x轴和y轴的刻度
+            for ax in g.axes.flat:
+                ax.xaxis.set_major_locator(MultipleLocator(150))
+                ax.set_xlim(-20, confData.rated_power * (1+0.2))
+
+                ax.yaxis.set_major_locator(MultipleLocator(2))
+                ax.set_ylim(-1, 18)
+
+                ax.set_xlabel(x_name)
+                ax.set_ylabel(y_name)
+
+            # 设置x轴刻度值旋转角度为45度  
+            plt.tick_params(axis='x', rotation=45)
+            # 调整布局和设置标题
+            plt.tight_layout()
+            plt.title(f'{Field_NameOfTurbine}={name}')
+
+            # 保存图像并关闭绘图窗口
+            output_file = os.path.join(outputAnalysisDir, f"{name}.png")
+            plt.savefig(output_file, bbox_inches='tight', dpi=120)
+            plt.close()

+ 29 - 53
dataAnalysisBusiness/algorithm/powerCurveAnalyst.py → wtoaamapi/apps/business/algorithm/powerCurveAnalyst.py

@@ -11,9 +11,9 @@ import seaborn as sns
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import charset_unify,Field_NameOfTurbine,ConfBusiness
 
 class PowerCurveAnalyst(Analyst):
     """
@@ -23,15 +23,22 @@ class PowerCurveAnalyst(Analyst):
 
     def typeAnalyst(self):
         return "power_curve"
-    
+
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
         if len(dataFrameMerge)<=0:
             print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
             return
         
-        self.drawOfPowerCurve(dataFrameMerge, outputAnalysisDir, confData,self.dataFrameContractOfTurbine)
-        # self.drawOfPowerCurveScatter(dataFrameMerge,outputAnalysisDir,confData,dataFrameGuaranteePowerCurve)
-        
+        dataFrameGuaranteePowerCurve=self.contractGuaranteePowerCurveData(confData.turbineGuaranteedPowerCurveFilePathCSV)
+        self.drawOfPowerCurve(dataFrameMerge, outputAnalysisDir, confData,dataFrameGuaranteePowerCurve)
+        self.drawOfPowerCurveScatter(dataFrameMerge,outputAnalysisDir,confData,dataFrameGuaranteePowerCurve)
+
+    def contractGuaranteePowerCurveData(self,csvPowerCurveFilePath):
+        dataFrameGuaranteePowerCurve=pd.read_csv(csvPowerCurveFilePath, encoding=charset_unify)
+
+        return dataFrameGuaranteePowerCurve
+
+
     def drawOfPowerCurveScatter(self, dataFrame: pd.DataFrame,  outputAnalysisDir, confData: ConfBusiness,dataFrameGuaranteePowerCurve:pd.DataFrame):
         """  
         绘制风速-功率分布图并保存为文件。  
@@ -64,22 +71,13 @@ class PowerCurveAnalyst(Analyst):
             
             # 设置图形标题和坐标轴标签  
             ax.set_title(f'turbine_name={name}')  
-            
-            # 设置坐标轴的主刻度定位器  
-            ax.xaxis.set_major_locator(MultipleLocator(1)) 
-            ax.set_xlim(0, 26)   
-
-            # 创建每100个单位一个刻度的定位器
-            yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-                confData.graphSets["activePower"]) and not self.common.isNone(
-                confData.graphSets["activePower"]["step"]) else 250)
-            ax.yaxis.set_major_locator(yloc)  # 将定位器应用到y轴上
-            ax.set_ylim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                        confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
-
-
+            ax.set_xlim(0, 25)  
             ax.set_xlabel(x_name)  
             ax.set_ylabel(y_name)  
+            
+            # 设置坐标轴的主刻度定位器  
+            ax.xaxis.set_major_locator(MultipleLocator(0.5))  
+            ax.yaxis.set_major_locator(MultipleLocator(200))  
                         
             # 显示图例,并调整位置  
             ax.legend(loc='lower right')  
@@ -116,9 +114,12 @@ class PowerCurveAnalyst(Analyst):
         confData (ConfBusiness): 配置 
         """
         
-        # 定义风速区间        
-        bins = np.arange(0, 26, 0.5)
+        # 定义风速区间
+        windSpeedMax=dataFrameMerge[confData.field_wind_speed].notnull().max()
+        # bins = np.arange(-0.25, windSpeedMax, 0.5)
         
+        bins = np.arange(-0.25, 25.75, 0.5)
+
         # 初始化结果DataFrame
         all_res = pd.DataFrame()
 
@@ -136,12 +137,12 @@ class PowerCurveAnalyst(Analyst):
         ress = all_res.reset_index(drop=True)        
         
         self.plot_power_curve(ress,  outputAnalysisDir, dataFrameGuaranteePowerCurve,Field_NameOfTurbine,
-                              '全场-{}功率曲线.png'.format(confData.farm_name),confData)
+                              '全场-{}功率曲线.png'.format(confData.farm_name))
 
         # 绘制每个设备的功率曲线图
         grouped=ress.groupby(Field_NameOfTurbine)
         for name, group in grouped:
-            self.plot_single_power_curve(ress, group,dataFrameGuaranteePowerCurve, name, outputAnalysisDir,confData)
+            self.plot_single_power_curve(ress, group,dataFrameGuaranteePowerCurve, name, outputAnalysisDir)
 
     def power_curve_helper(self, group, wind_speed_col, power_col, bins):
         """  
@@ -157,7 +158,7 @@ class PowerCurveAnalyst(Analyst):
         act_line.columns = ['风速区间', '有效数量', '实际功率曲线']
         return act_line
 
-    def plot_power_curve(self, ress, output_path,dataFrameGuaranteePowerCurve:pd.DataFrame, Field_NameOfTurbine, filename,confData:ConfBusiness):
+    def plot_power_curve(self, ress, output_path,dataFrameGuaranteePowerCurve:pd.DataFrame, Field_NameOfTurbine, filename):
         """  
         绘制全场功率曲线图。  
         """
@@ -170,25 +171,14 @@ class PowerCurveAnalyst(Analyst):
         ax.set_xlabel('wind speed')
         ax.set_ylabel('power')
         ax.set_title('power curve')
-        # 设置坐标轴的主刻度定位器  
-        ax.xaxis.set_major_locator(MultipleLocator(1))   
-        ax.set_xlim(0, 26)  
-
-        # 创建每100个单位一个刻度的定位器
-        yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-            confData.graphSets["activePower"]) and not self.common.isNone(
-            confData.graphSets["activePower"]["step"]) else 250)
-        ax.yaxis.set_major_locator(yloc)  # 将定位器应用到y轴上
-        ax.set_ylim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                    confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
-        
+        # plt.legend(ncol=4, loc='upper right')
         plt.legend(title='turbine',bbox_to_anchor=(1.02, 0.5),ncol=2, loc='center left', borderaxespad=0.) 
         plt.xticks(rotation=45)  # 旋转45度
         plt.savefig(os.path.join(output_path, filename),
                     bbox_inches='tight', dpi=120)
         plt.close()
 
-    def plot_single_power_curve(self, ress, group,dataFrameGuaranteePowerCurve:pd.DataFrame , turbineName, outputAnalysisDir,confData:ConfBusiness):
+    def plot_single_power_curve(self, ress, group,dataFrameGuaranteePowerCurve:pd.DataFrame , turbineName, outputAnalysisDir):
         
         color = ["lightgrey"]*len(ress[Field_NameOfTurbine].unique())
         fig, ax = plt.subplots(figsize=(8, 8))
@@ -204,20 +194,6 @@ class PowerCurveAnalyst(Analyst):
         ax.set_xlabel('wind speed')
         ax.set_ylabel('power')
         ax.set_title('turbine_name={}'.format(turbineName))
-        # 设置坐标轴的主刻度定位器  
-        ax.xaxis.set_major_locator(MultipleLocator(1))  
-        ax.set_xlim(0, 26)  
-
-        # 创建每100个单位一个刻度的定位器
-        yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
-            confData.graphSets["activePower"]) and not self.common.isNone(
-            confData.graphSets["activePower"]["step"]) else 250)
-        ax.yaxis.set_major_locator(yloc)  # 将定位器应用到y轴上
-        ax.set_ylim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
-                    confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
-        
-        # 显示图例,并调整位置  
-        ax.legend(loc='lower right')  
         plt.xticks(rotation=45)  # 旋转45度
         plt.savefig(outputAnalysisDir + r"/{}-curve.png".format(turbineName),
                     bbox_inches='tight', dpi=120)

+ 6 - 6
dataAnalysisBusiness/algorithm/powerOscillationAnalyst.py → wtoaamapi/apps/business/algorithm/powerOscillationAnalyst.py

@@ -3,9 +3,9 @@ import pandas as pd
 import numpy as np
 import matplotlib.pyplot as plt  
 import seaborn as sns  
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
 
 class PowerOscillationAnalyst(Analyst):
     """
@@ -57,10 +57,10 @@ class PowerOscillationAnalyst(Analyst):
         - farm_name: str, name of the wind farm.
         - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
         """        
-        sns.set_palette('deep')
         field_Name_Turbine = "turbine_name"
         x_name = 'power_col_floor'  
         y_name = 'speed_diff'  
+        split_way = '_power_diff.csv' 
 
         # 初始化结果DataFrame  
         res = pd.DataFrame()  
@@ -69,14 +69,14 @@ class PowerOscillationAnalyst(Analyst):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):  
             for file_name in file_names:  
 
-                if not file_name.endswith(CSVSuffix):
+                if not file_name.endswith(".csv"):
                     continue
 
                 file_path = os.path.join(root, file_name)  
                 frame = pd.read_csv(file_path, encoding=encoding)  
                 
                 # 获取输出文件名前缀  
-                turbine_name = file_name.split(CSVSuffix)[0]  
+                turbine_name = file_name.split(split_way)[0]  
                 # 添加设备名作为新列
                 frame[field_Name_Turbine] = turbine_name
                  

+ 13 - 24
dataAnalysisBusiness/algorithm/ratedPowerWindSpeedAnalyst.py → wtoaamapi/apps/business/algorithm/ratedPowerWindSpeedAnalyst.py

@@ -12,9 +12,9 @@ import seaborn as sns
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import *
 
 
 class RatedPowerWindSpeedAnalyst(Analyst):
@@ -38,32 +38,23 @@ class RatedPowerWindSpeedAnalyst(Analyst):
         outputAnalysisDir (str): 分析输出目录。  
         confData (ConfBusiness): 配置
         """
-        # 检查所需列是否存在
-        required_columns = {confData.field_env_temp,
-                            confData.field_wind_speed, confData.field_power}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-
-        y_name = 'power'
-        upLimitOfPower = confData.rated_power*1.1
-        lowLimitOfPower = confData.rated_power*0.9
+        y_name='power'
         # 根据环境温度筛选数据
         over_temp = dataFrameMerge[(dataFrameMerge[confData.field_env_temp] >= 25) & (
-            dataFrameMerge[confData.field_wind_speed] >= confData.rated_WindSpeed) & (dataFrameMerge[confData.field_power] >= lowLimitOfPower)].sort_values(by=Field_NameOfTurbine)
+            dataFrameMerge[confData.field_wind_speed] >= confData.rated_WindSpeed)]
         below_temp = dataFrameMerge[(dataFrameMerge[confData.field_env_temp] < 25) & (
-            dataFrameMerge[confData.field_wind_speed] >= confData.rated_WindSpeed) & (dataFrameMerge[confData.field_power] >= lowLimitOfPower)].sort_values(by=Field_NameOfTurbine)
+            dataFrameMerge[confData.field_wind_speed] >= confData.rated_WindSpeed)]
 
         # 绘制环境温度大于等于25℃的功率分布图
+        # fig, ax = plt.subplots(figsize=(16, 8))
         fig, ax = plt.subplots()
         sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=over_temp, fliersize=0, ax=ax,
                     medianprops={'linestyle': '-', 'color': 'red'},
                     boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
-        ax.yaxis.set_major_locator(ticker.MultipleLocator(100))
-        ax.set_ylim(lowLimitOfPower, upLimitOfPower)
+        # ax.yaxis.set_major_locator(ticker.MultipleLocator(20))
+        # ax.set_ylim(confData.rated_power*0.55, confData.rated_power*1.1)
         ax.set_ylabel(y_name)
-        ax.set_title(
-            'rated wind speed and power distribute(10min)(ambient temperature>=25℃)')
-        ax.grid(True)
+        ax.set_title('rated wind speed and power distribute(10min)(ambient temperature>=25℃)')
         plt.xticks(rotation=45)  # 旋转45度
         plt.savefig(os.path.join(outputAnalysisDir,
                     "额定满发风速功率分布(10min)(环境温度大于25度).png"), bbox_inches='tight', dpi=120)
@@ -74,12 +65,10 @@ class RatedPowerWindSpeedAnalyst(Analyst):
         sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=below_temp, fliersize=0, ax=ax,
                     medianprops={'linestyle': '-', 'color': 'red'},
                     boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
-        ax.yaxis.set_major_locator(ticker.MultipleLocator(100))
-        ax.set_ylim(lowLimitOfPower, upLimitOfPower)
+        # ax.yaxis.set_major_locator(ticker.MultipleLocator(10))
+        # ax.set_ylim(confData.rated_power*0.6, confData.rated_power*1.1)
         ax.set_ylabel(y_name)
-        ax.set_title(
-            'rated wind speed and power distribute(10min)(ambient temperature<25℃)')
-        ax.grid(True)
+        ax.set_title('rated wind speed and power distribute(10min)(ambient temperature<25℃)')
         plt.xticks(rotation=45)  # 旋转45度
         plt.savefig(os.path.join(outputAnalysisDir,
                     "额定满发风速功率分布(10min)(环境温度小于25度).png"), bbox_inches='tight', dpi=120)

+ 4 - 4
dataAnalysisBusiness/algorithm/ratedWindSpeedAnalyst.py → wtoaamapi/apps/business/algorithm/ratedWindSpeedAnalyst.py

@@ -11,9 +11,9 @@ import seaborn as sns
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import *
 
 
 class RatedWindSpeedAnalyst(Analyst):
@@ -57,7 +57,7 @@ class RatedWindSpeedAnalyst(Analyst):
         sns.barplot(x=Field_NameOfTurbine, y='count',
                     data=data, ax=ax, color='dodgerblue')
         ax.set_title('Rated - full wind speed interval data count')
-        ax.grid(True)
+        
         # 旋转45度
         plt.xticks(rotation=45)  
         # 保存图表

+ 11 - 16
dataAnalysisBusiness/algorithm/temperatureEnvironmentAnalyst.py → wtoaamapi/apps/business/algorithm/temperatureEnvironmentAnalyst.py

@@ -7,10 +7,10 @@ import seaborn as sns
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
-from behavior.analyst import Analyst
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
-import common.turbineInfo as turbineInfo
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import charset_unify,Field_NameOfTurbine,ConfBusiness
+import turbineInfo
 
 
 class TemperatureEnvironmentAnalyst(Analyst):
@@ -21,20 +21,14 @@ class TemperatureEnvironmentAnalyst(Analyst):
     def typeAnalyst(self):
         return "temperature_environment"
 
-    def turbinesAnalysis(self, dataFrameMerge,outputAnalysisDir, confData: ConfBusiness):        
-        # 检查所需列是否存在
-        required_columns = {confData.field_env_temp,
-                            Field_NameOfTurbine}
-        if not required_columns.issubset(dataFrameMerge.columns):
-            raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
-        
+    def turbinesAnalysis(self, dataFrameMerge,outputAnalysisDir, confData: ConfBusiness):
         turbineInfos = turbineInfo.loadTurbineInfo(confData.turbineInfoFilePathCSV)
 
         #  环境温度 分析
         turbineEnvTempData =dataFrameMerge.groupby(Field_NameOfTurbine).agg(
-            {confData.field_env_temp : 'median'}).reset_index(names=[Field_NameOfTurbine]) 
+            {confData.field_env_temp : 'mean'}).reset_index(names=[Field_NameOfTurbine]) 
         mergeData = self.merge_Data(Field_NameOfTurbine,turbineInfos, turbineEnvTempData,confData)
-        
+        print(mergeData.head())
         self.draw(mergeData,outputAnalysisDir, confData)
     
 
@@ -58,7 +52,8 @@ class TemperatureEnvironmentAnalyst(Analyst):
         'left': 左连接,保留左边DataFrame的所有键,以及右边DataFrame中匹配的键的行。
         'right': 右连接,保留右边DataFrame的所有键,以及左边DataFrame中匹配的键的行。
         """
-        # turbineEnvTempData[fieldTurbineName]=turbineEnvTempData[fieldTurbineName].astype(str)
+        turbineInfos[fieldTurbineName]=turbineInfos[fieldTurbineName].astype(str).apply(confData.add_W_if_starts_with_digit)
+        turbineEnvTempData[fieldTurbineName]=turbineEnvTempData[fieldTurbineName].astype(str)
         merge_data = pd.merge(turbineInfos, turbineEnvTempData, on=[fieldTurbineName], how='inner')
 
         return merge_data
@@ -89,7 +84,7 @@ class TemperatureEnvironmentAnalyst(Analyst):
             target_tuple = (center[0], current_temp)
             if target_tuple in nearby_points:
                 nearby_points.remove(target_tuple)
-            mean_temp = np.median([i[1] for i in nearby_points]) if nearby_points else current_temp
+            mean_temp = np.mean([i[1] for i in nearby_points]) if nearby_points else current_temp
             res.append((center[0], nearby_points, mean_temp, current_temp))
         res = pd.DataFrame(res, columns=[Field_NameOfTurbine, '周边机组', '周边机组温度', '当前机组温度'])
         res[self.fieldTemperatureDiff] = res['当前机组温度'] - res['周边机组温度']
@@ -112,5 +107,5 @@ class TemperatureEnvironmentAnalyst(Analyst):
         
         sns.barplot(x=Field_NameOfTurbine ,y='当前机组温度',data=res,ax=ax2,color='dodgerblue')
         ax2.set_ylabel('temperature')
-        ax2.set_title('temperature median')
+        ax2.set_title('temperature mean')
         plt.savefig(outputAnalysisDir +'//'+ "{}环境温度均值.png".format(confData.farm_name),bbox_inches='tight',dpi=120)

+ 143 - 0
wtoaamapi/apps/business/algorithm/temperatureLargeComponentsAnalyst.py

@@ -0,0 +1,143 @@
+import os
+import pandas as pd
+import numpy as np
+import pandas as pd  
+import matplotlib.pyplot as plt  
+import seaborn as sns  
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
+
+class TemperatureLargeComponentsAnalyst(Analyst):
+    """
+    风电机组大部件温升分析
+    """
+
+    def typeAnalyst(self):
+        return "temperature_large_components"
+
+    def turbineAnalysis(self,
+                 dataFrame,
+                 outputAnalysisDir,
+                 outputFilePath,
+                 confData: ConfBusiness,
+                 turbineName):
+
+        self.temp_power(dataFrame, outputFilePath,
+                        confData.field_turbine_time,confData.field_power,confData.field_temperature_large_components)
+        
+    def getNoneEmptyFields(self,dataFrame,temperatureFields):
+        # 检查指定列中非全为空的列
+        non_empty_columns = dataFrame[temperatureFields].apply(
+            lambda x: x.notnull().any(), axis=0)
+        # 获取非全为空的列名
+        noneEmptyFields = non_empty_columns[non_empty_columns].index.tolist()
+        return noneEmptyFields
+
+    def temp_power(self, dataFrame, output_path,  field_time, field_power_active, field_temperature_large_componts):
+        # Convert the string list of temperature columns into a list
+        print("field_temperature_large_componts is {}".format(field_temperature_large_componts))
+        temperature_cols = field_temperature_large_componts.split(',')
+        
+        useCols = []
+        useCols.append(field_time)
+        useCols.append(field_power_active)
+        useCols.extend(temperature_cols)
+
+        # 获取非全为空的列名
+        non_empty_cols =self.getNoneEmptyFields(dataFrame,temperature_cols)
+        
+        # 清洗数据
+        dataFrame=dataFrame[useCols]
+        dataFrame = dataFrame.dropna(axis=1, how='all')
+        dataFrame = dataFrame.dropna(axis=0)
+        
+        # Calculate 'power_floor'
+        dataFrame['power_floor'] = (dataFrame[field_power_active] / 10).astype(int) * 10
+
+        # Initialize an empty DataFrame for aggregation
+        agg_dict = {col: 'mean' for col in non_empty_cols}
+
+        # Group by 'power_floor' and aggregate
+        grouped = dataFrame.groupby('power_floor').agg(agg_dict).reset_index()
+
+        # Sort by 'power_floor'
+        grouped.sort_values('power_floor', inplace=True)
+
+        # Write to CSV
+        grouped.to_csv(output_path, index=False)
+    
+    def turbinesAnalysis(self, dataFrameMerge,outputAnalysisDir, confData: ConfBusiness):
+        self.plot_temperature_distribution(outputAnalysisDir,confData,confData.field_temperature_large_components)
+  
+    def plot_temperature_distribution(self,csvFileDirOfCp, confData: ConfBusiness, field_temperature_large_componts,encoding='utf-8'):
+        """
+        Generates Cp distribution plots for turbines in a wind farm.
+
+        Parameters:
+        - csvFileDirOfCp: str, path to the directory containing input CSV files.
+        - farm_name: str, name of the wind farm.
+        - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
+        """ 
+        field_Name_Turbine= "turbine_name"
+        x_name = 'power_floor'  
+        y_name = 'temperature'  
+        split_way = '_temperature_large_components.csv' 
+        
+        columns = field_temperature_large_componts.split(',')
+        # Create output directories if they don't exist  
+        for column in columns:  
+            type_name = '{}'.format(column)  
+            output_path = os.path.join(csvFileDirOfCp, type_name)  
+            os.makedirs(output_path, exist_ok=True)  
+            print("current column {}".format(column))
+
+            sns.set_palette('deep')
+            # Initialize DataFrame to store concatenated data  
+            res = pd.DataFrame()  
+    
+            # Iterate over files in the input path  
+            for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):  
+                for file_name in file_names:                
+
+                    if not file_name.endswith(".csv"):
+                        continue
+
+                    print(os.path.join(root, file_name))  
+                    frame = pd.read_csv(os.path.join(root, file_name), encoding=encoding)  
+
+                    if column not in frame.columns:
+                        continue
+                    
+                    # 获取输出文件名(不含split_way之后的部分)  
+                    turbineName = file_name.split(split_way)[0]  
+                    # 添加设备名作为新列  
+                    frame[field_Name_Turbine] = confData.add_W_if_starts_with_digit(turbineName)
+
+                    res = pd.concat([res, frame.loc[:, [field_Name_Turbine, x_name, column]]], axis=0)  
+    
+            # Reset index and plot  
+            ress = res.reset_index()  
+            fig, ax2 = plt.subplots()  
+            ax2 = sns.lineplot(x=x_name, y=column, data=ress, hue=field_Name_Turbine)  
+            # ax2.set_xlim(-150, 2100)  
+            ax2.set_xlabel(x_name)
+            ax2.set_ylabel(y_name)
+            ax2.set_title('Temperature-Distribute')  
+            plt.legend(bbox_to_anchor=(1.02, 0.5), loc='center left',ncol=2, borderaxespad=0.)  
+            plt.savefig(os.path.join(output_path, "{}.png".format(column)), bbox_inches='tight', dpi=120)  
+            plt.close()  
+    
+            # Plot individual device lines  
+            grouped = ress.groupby(field_Name_Turbine)  
+            for name, group in grouped:  
+                color = ["lightgrey"] * len(ress[field_Name_Turbine].unique())  
+                fig, ax = plt.subplots()  
+                ax = sns.lineplot(x=x_name, y=column, data=ress, hue=field_Name_Turbine, palette=sns.set_palette(color), legend=False)  
+                ax = sns.lineplot(x=x_name, y=column, data=group, color='darkblue', legend=False)   
+                ax.set_xlabel(x_name)
+                ax.set_ylabel(y_name)
+                ax.set_title('turbine_name={}'.format(name))  
+                # ax.set_xlim(-150, 2100)  
+                plt.savefig(os.path.join(output_path, "{}.png".format(name)), bbox_inches='tight', dpi=120)  
+                plt.close()  

+ 17 - 42
dataAnalysisBusiness/algorithm/tsrAnalyst.py → wtoaamapi/apps/business/algorithm/tsrAnalyst.py

@@ -4,13 +4,12 @@ import numpy as np
 import pandas as pd
 import matplotlib.pyplot as plt
 import seaborn as sns
-from matplotlib.ticker import MultipleLocator
-from behavior.analystExcludeRatedPower import AnalystExcludeRatedPower
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
 
 
-class TSRAnalyst(AnalystExcludeRatedPower):
+class TSRAnalyst(Analyst):
     """
     风电机组叶尖速比分析
     """
@@ -36,10 +35,10 @@ class TSRAnalyst(AnalystExcludeRatedPower):
         # Ensure the necessary columns are of float type
         dataFrame['wind_speed'] = dataFrame[field_wind_speed].astype(float)
         dataFrame['rotor_speed'] = dataFrame[field_rotort_speed].astype(float)
-        # rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce')
-        # # Calculate TSR
-        # dataFrame['tsr'] = (dataFrame['rotor_speed'] * 0.104667 *
-        #                     (rotor_diameter / 2)) / dataFrame['wind_speed']
+        rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce')
+        # Calculate TSR
+        dataFrame['tsr'] = (dataFrame['rotor_speed'] * 0.104667 *
+                            (rotor_diameter / 2)) / dataFrame['wind_speed']
 
         # Group by 'power_floor' and calculate mean, max, and min of TSR
         grouped = dataFrame.groupby('power_floor').agg({
@@ -59,11 +58,11 @@ class TSRAnalyst(AnalystExcludeRatedPower):
         grouped.to_csv(output_path, index=False)
 
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.plot_tsr_distribution(outputAnalysisDir, confData)
+        self.plot_tsr_distribution(outputAnalysisDir, confData.farm_name)
 
-    def plot_tsr_distribution(self, csvFileDirOfCp, confData: ConfBusiness, encoding='utf-8'):
+    def plot_tsr_distribution(self, csvFileDirOfCp, farm_name, encoding='utf-8'):
         """
-        Generates tsr distribution plots for turbines in a wind farm.
+        Generates Cp distribution plots for turbines in a wind farm.
 
         Parameters:
         - csvFileDirOfCp: str, path to the directory containing input CSV files.
@@ -73,9 +72,7 @@ class TSRAnalyst(AnalystExcludeRatedPower):
         field_Name_Turbine = "turbine_name"
         x_name = 'power_floor'
         y_name = 'tsr'
-
-        upLimitOfPower = confData.rated_power*0.9
-        upLimitOfTSR = 20
+        split_way = '_tsr.csv'
 
         # 设置绘图样式
         sns.set_palette('deep')
@@ -87,16 +84,16 @@ class TSRAnalyst(AnalystExcludeRatedPower):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
             for file_name in file_names:
 
-                if not file_name.endswith(CSVSuffix):
+                if not file_name.endswith(".csv"):
                     continue
 
                 file_path = os.path.join(root, file_name)
 
                 # 读取CSV文件
                 frame = pd.read_csv(file_path, encoding=encoding)
-                frame = frame[(frame[x_name] > 0)]
+
                 # 提取设备名
-                turbine_name = file_name.split(CSVSuffix)[0]
+                turbine_name = file_name.split(split_way)[0]
 
                 # 添加设备名作为新列
                 frame[field_Name_Turbine] = turbine_name
@@ -112,21 +109,10 @@ class TSRAnalyst(AnalystExcludeRatedPower):
         fig, ax = plt.subplots(figsize=(16, 8))
         ax = sns.lineplot(x=x_name, y=y_name, data=ress,
                           hue=field_Name_Turbine)
-
-        ax.xaxis.set_major_locator(MultipleLocator(200))  # 创建一个刻度 ,将定位器应用到y轴上
-        ax.set_xlim(0, upLimitOfPower)
-
-        ax.yaxis.set_major_locator(MultipleLocator(
-            confData.graphSets["tsr"]["step"] if not self.common.isNone(confData.graphSets["tsr"]["step"]) else 5))  # 创建一个刻度 ,将定位器应用到y轴上
-        ax.set_ylim(confData.graphSets["tsr"]["min"] if not self.common.isNone(confData.graphSets["tsr"]["min"]) else 0,
-                    confData.graphSets["tsr"]["max"] if not self.common.isNone(confData.graphSets["tsr"]["max"]) else upLimitOfTSR)
-
         ax.set_title('TSR-Distibute')
         # plt.legend(ncol=4)
-        plt.xticks(rotation=45)  # 旋转45度
-        plt.legend(title='turbine', bbox_to_anchor=(1.02, 0.5),
-                   ncol=2, loc='center left', borderaxespad=0.)
-        plt.savefig(csvFileDirOfCp + r"/{}-TSR-Distibute.png".format(confData.farm_name),
+        plt.legend(title='turbine',bbox_to_anchor=(1.02, 0.5),ncol=2, loc='center left', borderaxespad=0.) 
+        plt.savefig(csvFileDirOfCp + r"/{}-TSR-Distibute.png".format(farm_name),
                     bbox_inches='tight', dpi=300)
         plt.close(fig)
 
@@ -139,18 +125,7 @@ class TSRAnalyst(AnalystExcludeRatedPower):
                               palette=sns.set_palette(color), legend=False)
             ax = sns.lineplot(x=x_name, y=y_name, data=group,
                               color='darkblue', legend=False)
-
-            ax.xaxis.set_major_locator(
-                MultipleLocator(200))  # 创建一个刻度 ,将定位器应用到y轴上
-            ax.set_xlim(0, upLimitOfPower)
-
-            ax.yaxis.set_major_locator(MultipleLocator(
-                confData.graphSets["tsr"]["step"] if not self.common.isNone(confData.graphSets["tsr"]["step"]) else 5))  # 创建一个刻度 ,将定位器应用到y轴上
-            ax.set_ylim(confData.graphSets["tsr"]["min"] if not self.common.isNone(confData.graphSets["tsr"]["min"]) else 0,
-                        confData.graphSets["tsr"]["max"] if not self.common.isNone(confData.graphSets["tsr"]["max"]) else upLimitOfTSR)
-
             ax.set_title('turbine name={}'.format(name))
-            plt.xticks(rotation=45)  # 旋转45度
             plt.savefig(csvFileDirOfCp + r"/{}.png".format(name),
                         bbox_inches='tight', dpi=120)
             plt.close(fig)

+ 125 - 0
wtoaamapi/apps/business/algorithm/tsrTrendAnalyst.py

@@ -0,0 +1,125 @@
+import os
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+import seaborn as sns
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
+
+
+class TSRTrendAnalyst(Analyst):
+    """
+    风电机组叶尖速比时序分析
+    """
+
+    def typeAnalyst(self):
+        return "tsr_trend"
+
+    def turbineAnalysis(self,
+                        dataFrame,
+                        outputAnalysisDir,
+                        outputFilePath,
+                        confData: ConfBusiness,
+                        turbineName):
+
+        self.tsr_trend(dataFrame, outputFilePath,
+                       confData.field_turbine_time, confData.field_wind_speed, confData.field_rotor_speed, confData.field_power, confData.field_pitch_angle1,
+                       confData.rotor_diameter)
+
+    def tsr_trend(self, dataFrame, output_path, field_time, field_wind_speed, field_rotor_speed, field_power_active, field_angle_pitch, rotor_diameter):
+        # Convert time column to datetime and extract date
+        dataFrame[field_time] = pd.to_datetime(
+            dataFrame[field_time], format='%Y-%m-%d %H:%M:%S')
+        dataFrame['time_day'] = dataFrame[field_time].dt.date
+
+        # Calculate 'tsr'
+        dataFrame['wind_speed'] = dataFrame[field_wind_speed].astype(float)
+        dataFrame['rotor_speed'] = dataFrame[field_rotor_speed].astype(float)
+        rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce')
+        dataFrame['tsr'] = (dataFrame['rotor_speed'] * 0.104667 *
+                            (rotor_diameter / 2)) / dataFrame['wind_speed']
+
+        # Group by day and aggregate
+        grouped = dataFrame.groupby('time_day').agg({
+            field_time: 'min',
+            'wind_speed': 'mean',
+            'rotor_speed': 'mean',
+            'tsr': ['mean', 'max', 'min']
+        }).reset_index()
+
+        # Rename columns post-aggregation
+        grouped.columns = ['time_day', 'time_', 'wind_speed',
+                           'rotor_speed', 'tsr', 'tsr_max', 'tsr_min']
+
+        # Sort by day
+        grouped.sort_values('time_day', inplace=True)
+
+        # Write to CSV
+        grouped.to_csv(output_path, index=False)
+
+    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
+        self.plot_tsr_trend(outputAnalysisDir, confData.farm_name)
+
+    def plot_tsr_trend(self, csvFileDirOfCp, farm_name, encoding='utf-8'):
+        """  
+        Plot TSR trend from CSV files in a given input path and save the plots to an output path.  
+
+        Parameters:  
+        - csvFileDirOfCp: str, path to the directory containing input CSV files.
+        - farm_name: str, name of the wind farm.
+        - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
+        """
+        field_Name_Turbine = "turbine_name"
+        file_time = 'time_day'
+        y_name = 'tsr'
+        y_min = 'tsr_min'
+        y_max = 'tsr_max'
+        split_way = '_tsr_trend.csv'
+
+        # Set seaborn palette
+        sns.set_palette('deep')
+
+        # Iterate over the files in the input directory
+        for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
+            for file_name in file_names:
+
+                if not file_name.endswith(".csv"):
+                    continue
+
+                try:
+                    print(os.path.join(root, file_name))
+                    data = pd.read_csv(os.path.join(
+                        root, file_name), encoding=encoding)
+
+                    # Convert the time column to datetime
+                    data.loc[:, file_time] = pd.to_datetime(
+                        data.loc[:, file_time])
+
+                    # Calculate min and max TSR values
+                    data[y_min] = data[y_name] - data[y_min]
+                    data[y_max] = data[y_max] - data[y_name]
+
+                    # Split the file name to get the output name
+                    turbine_name = file_name.split(split_way)[0]
+                    # 添加设备名作为新列
+                    data[field_Name_Turbine] = turbine_name
+
+                    # Plot the TSR trend with error bars
+                    fig, ax = plt.subplots()
+                    ax.errorbar(x=data[file_time], y=data[y_name], yerr=[data[y_min], data[y_max]],
+                                fmt='o', capsize=4, elinewidth=2, ecolor='lightgrey', mfc='dodgerblue')
+
+                    # Set axis labels, limits, and title
+                    ax.set_xlabel('time')
+                    ax.set_ylabel('TSR')
+                    # ax.set_ylim(0, 16)
+                    ax.set_title('turbine_name={}'.format(turbine_name))
+
+                    # Save the plot to the output path
+                    plt.savefig(os.path.join(csvFileDirOfCp, "{}.png".format(
+                        turbine_name)), bbox_inches='tight', dpi=120)
+                    plt.close(fig)
+
+                except Exception as e:
+                    print(f"An error occurred while processing file {file_name}: {e}")

+ 15 - 37
dataAnalysisBusiness/algorithm/tsrWindSpeedAnalyst.py → wtoaamapi/apps/business/algorithm/tsrWindSpeedAnalyst.py

@@ -3,14 +3,13 @@ import pandas as pd
 import numpy as np
 import pandas as pd
 import matplotlib.pyplot as plt
-from matplotlib.ticker import MultipleLocator
 import seaborn as sns
-from behavior.analystExcludeRatedPower import AnalystExcludeRatedPower
-from utils.directoryUtil import DirectoryUtil as dir
-from algorithmContract.confBusiness import *
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
 
 
-class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
+class TSRWindSpeedAnalyst(Analyst):
     """
     风电机组叶尖速比分析
     """
@@ -36,10 +35,10 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
         # Ensure the necessary columns are of float type
         dataFrame['wind_speed'] = dataFrame[field_wind_speed].astype(float)
         dataFrame['rotor_speed'] = dataFrame[field_rotort_speed].astype(float)
-        # rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce')
-        # # Calculate TSR
-        # dataFrame['tsr'] = (dataFrame['rotor_speed'] * 0.104667 *
-        #                     (rotor_diameter / 2)) / dataFrame['wind_speed']
+        rotor_diameter = pd.to_numeric(rotor_diameter, errors='coerce')
+        # Calculate TSR
+        dataFrame['tsr'] = (dataFrame['rotor_speed'] * 0.104667 *
+                            (rotor_diameter / 2)) / dataFrame['wind_speed']
 
         # Group by 'wind_speed_floor' and calculate mean, max, and min of TSR
         grouped = dataFrame.groupby('wind_speed_floor').agg({
@@ -59,11 +58,11 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
         grouped.to_csv(output_path, index=False)
 
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
-        self.plot_tsr_distribution(outputAnalysisDir, confData)
+        self.plot_tsr_distribution(outputAnalysisDir, confData.farm_name)
 
-    def plot_tsr_distribution(self, csvFileDirOfCp, confData: ConfBusiness, encoding='utf-8'):
+    def plot_tsr_distribution(self, csvFileDirOfCp, farm_name, encoding='utf-8'):
         """
-        Generates tsr distribution plots for turbines in a wind farm.
+        Generates Cp distribution plots for turbines in a wind farm.
 
         Parameters:
         - csvFileDirOfCp: str, path to the directory containing input CSV files.
@@ -73,8 +72,7 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
         field_Name_Turbine = "turbine_name"
         x_name = 'wind_speed_floor'
         y_name = 'tsr'
-
-        upLimitOfTSR=20
+        split_way = '_tsr_windspeed.csv'
 
         # 设置绘图样式
         sns.set_palette('deep')
@@ -86,7 +84,7 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
             for file_name in file_names:
 
-                if not file_name.endswith(CSVSuffix):
+                if not file_name.endswith(".csv"):
                     continue
 
                 file_path = os.path.join(root, file_name)
@@ -95,7 +93,7 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
                 frame = pd.read_csv(file_path, encoding=encoding)
 
                 # 提取设备名
-                turbine_name = file_name.split(CSVSuffix)[0]
+                turbine_name = file_name.split(split_way)[0]
 
                 # 添加设备名作为新列
                 frame[field_Name_Turbine] = turbine_name
@@ -111,19 +109,9 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
         fig, ax = plt.subplots(figsize=(16, 8))
         ax = sns.lineplot(x=x_name, y=y_name, data=ress,
                           hue=field_Name_Turbine)
-        
-        ax.xaxis.set_major_locator(MultipleLocator(1)) # 创建一个刻度 ,将定位器应用到y轴上 
-        ax.set_xlim(0,26)
-
-        ax.yaxis.set_major_locator(MultipleLocator(
-            confData.graphSets["tsr"]["step"] if not self.common.isNone(confData.graphSets["tsr"]["step"]) else 5))  # 创建一个刻度 ,将定位器应用到y轴上
-        ax.set_ylim(confData.graphSets["tsr"]["min"] if not self.common.isNone(confData.graphSets["tsr"]["min"]) else 0,
-                    confData.graphSets["tsr"]["max"] if not self.common.isNone(confData.graphSets["tsr"]["max"]) else upLimitOfTSR)
-        
         ax.set_title('TSR-Distibute')
         plt.legend(ncol=4)
-        plt.xticks(rotation=45)  # 旋转45度
-        plt.savefig(csvFileDirOfCp + r"/{}-TSR-Distibute.png".format(confData.farm_name),
+        plt.savefig(csvFileDirOfCp + r"/{}-TSR-Distibute.png".format(farm_name),
                     bbox_inches='tight', dpi=300)
         plt.close(fig)
 
@@ -136,17 +124,7 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
                               palette=sns.set_palette(color), legend=False)
             ax = sns.lineplot(x=x_name, y=y_name, data=group,
                               color='darkblue', legend=False)
-            
-            ax.xaxis.set_major_locator(MultipleLocator(1)) # 创建一个刻度 ,将定位器应用到y轴上 
-            ax.set_xlim(0,26)
-            
-            ax.yaxis.set_major_locator(MultipleLocator(
-            confData.graphSets["tsr"]["step"] if not self.common.isNone(confData.graphSets["tsr"]["step"]) else 2))  # 创建一个刻度 ,将定位器应用到y轴上
-            ax.set_ylim(confData.graphSets["tsr"]["min"] if not self.common.isNone(confData.graphSets["tsr"]["min"]) else 0,
-                        confData.graphSets["tsr"]["max"] if not self.common.isNone(confData.graphSets["tsr"]["max"]) else upLimitOfTSR)
-
             ax.set_title('turbine name={}'.format(name))
-            plt.xticks(rotation=45)  # 旋转45度
             plt.savefig(csvFileDirOfCp + r"/{}.png".format(name),
                         bbox_inches='tight', dpi=120)
             plt.close(fig)

+ 0 - 0
appService/create → wtoaamapi/apps/business/algorithm/utils/__init__.py


+ 57 - 0
wtoaamapi/apps/business/algorithm/utils/csvFileUtil.py

@@ -0,0 +1,57 @@
+import pandas as pd
+
+class CSVFileUtil:
+    def __init__(self, filepath):
+        """
+        初始化CSV工具类
+        :param filepath: CSV文件的路径
+        """
+        self.filepath = filepath
+
+    def read_csv(self):
+        """
+        读取CSV文件
+        :return: 返回DataFrame对象
+        """
+        return pd.read_csv(self.filepath)
+    
+    def read_csv_columns(self,useColumns):
+        """
+        读取CSV文件
+        :return: 返回DataFrame对象
+        """
+        return pd.read_csv(self.filepath,usecols=useColumns)
+
+    def write_csv(self, data, dest_path):
+        """
+        将数据写入CSV文件
+        :param data: DataFrame对象或可转换为DataFrame的数据
+        :param dest_path: 保存CSV文件的路径
+        """
+        if not isinstance(data, pd.DataFrame):
+            data = pd.DataFrame(data)
+        data.to_csv(dest_path, index=False)
+
+    def select_and_save_columns(self, columns, dest_path):
+        """
+        选取指定的列并保存为新的CSV文件
+        :param columns: 要选取的列名列表
+        :param dest_path: 保存新CSV文件的路径
+        """
+        df = self.read_csv_columns(columns)
+        self.write_csv(df, dest_path)
+
+# 使用示例
+if __name__ == "__main__":
+    filepath = '/path/to/your/original.csv'  # 替换为你的CSV文件路径
+    dest_path = '/path/to/your/selected_columns.csv'  # 替换为你想保存选取列后的CSV文件路径
+    columns = ['时间','iTempOutdoor_1sec','iTempNacelle_1sec','iGenPower','iRotorSpeedPDM','iGenSpeed','iWindSpeed_real',
+               'iPitchAngle1','iPitchAngle2','iPitchAngle3','iNacellePositionTotal','iwindDirection','iVaneDiiection',
+               'iActivePoweiSetPointValue','iReactivePower','iKWhThisDay_h','iVibrationY','iVibrationZ','iTemp1GearOil_1sec',
+               'iTempRotorBearA_1sec','iTempGearBearNDE_1sec','iTempGearBearDE_1sec','iTempGenBearDE_1sec','iTempGenBearNDE_1sec',
+               'iTempGenStatorU_1sec','iTempHub_1sec','iTempCntr_1sec','WT_Runcode','WT_Faultcode','iTempRotorBearA_1sec']  # 替换为你想选取的列名
+
+    tool = CSVFileUtil(filepath)
+    tool.select_and_save_columns(columns, dest_path)
+
+

+ 89 - 0
wtoaamapi/apps/business/algorithm/utils/directoryUtil.py

@@ -0,0 +1,89 @@
+import os
+import shutil
+
+class DirectoryUtil:
+    @staticmethod
+    def create_directory(path):
+        """
+        创建一个新目录。如果目录已存在,则不执行任何操作。
+        :param path: 要创建的目录路径
+        """
+        try:
+            os.makedirs(path, exist_ok=True)
+            print(f"Directory '{path}' created successfully.")
+        except OSError as error:
+            print(f"Creating directory '{path}' failed. Error: {error}")
+
+    @staticmethod
+    def delete_directory(path):
+        """
+        删除一个目录及其所有内容。
+        :param path: 要删除的目录路径
+        """
+        try:
+            shutil.rmtree(path)
+            print(f"Directory '{path}' deleted successfully.")
+        except OSError as error:
+            print(f"Deleting directory '{path}' failed. Error: {error}")
+
+    @staticmethod
+    def list_directory_contents(path):
+        """
+        列出目录中的所有文件和子目录。
+        :param path: 目录路径
+        :return: 目录内容的列表
+        """
+        try:
+            contents = os.listdir(path)
+            print(f"Contents of directory '{path}': {contents}")
+            return contents
+        except OSError as error:
+            print(f"Listing contents of directory '{path}' failed. Error: {error}")
+            return []
+        
+    @staticmethod
+    def list_directory(path):
+        """
+        列出目录中的所有文件和子目录。
+        :param path: 目录路径
+        :return: 指定目录、子目录列表、文件列表
+        """
+        try:
+            return os.walk(path)
+        except OSError as error:
+            print(f"Listing contents of directory '{path}' failed. Error: {error}")
+            return path,None,None
+
+    @staticmethod
+    def check_directory_exists(path):
+        """
+        检查一个目录是否存在。
+        :param path: 目录路径
+        :return: 如果目录存在则返回True,否则返回False
+        """
+        return os.path.exists(path) and os.path.isdir(path)
+
+# 使用示例
+if __name__ == "__main__":
+    path = 'E:\\BaiduNetdiskDownload\\merge1'  # 替换为你的目录路径
+
+    # 创建目录
+    DirectoryUtil.create_directory(path)
+
+    # 检查目录是否存在
+    if DirectoryUtil.check_directory_exists(path):
+        print(f"Directory '{path}' exists.")
+
+    # 列出目录内容
+    DirectoryUtil.list_directory_contents(path)
+
+    results=DirectoryUtil.list_directory(path)
+
+    for root,dirs,files in results:
+        print(root)
+        print(dirs)
+        print(files)
+
+    # 删除目录
+    # 注意:这将删除目录及其所有内容,请谨慎操作!
+    # DirectoryTool.delete_directory(path)

+ 0 - 0
mydatabase.db → wtoaamapi/apps/business/algorithm/utils/jsonUtil/__init__.py


+ 37 - 0
wtoaamapi/apps/business/algorithm/utils/jsonUtil/jsonUtil.py

@@ -0,0 +1,37 @@
+import json
+
+class JsonUtil:
+    @staticmethod
+    def read_json(file_path, encoding='utf-8'):
+        """读取JSON文件"""
+        try:
+            with open(file_path, 'r', encoding=encoding) as f:
+                return json.load(f)
+        except FileNotFoundError:
+            print("文件不存在")
+            return None
+
+    @staticmethod
+    def write_json(data, file_path, encoding='utf-8'):
+        """写入JSON到文件"""
+        with open(file_path, 'w', encoding=encoding) as f:
+            json.dump(data, f, ensure_ascii=False, indent=4)
+
+    @staticmethod
+    def update_json(file_path, updates, encoding='utf-8'):
+        """更新JSON文件"""
+        data = JsonUtil.read_json(file_path, encoding)
+        if data is not None:
+            data.update(updates)
+            JsonUtil.write_json(data, file_path, encoding)
+
+    @staticmethod
+    def delete_key(file_path, key, encoding='utf-8'):
+        """从JSON文件删除特定键"""
+        data = JsonUtil.read_json(file_path, encoding)
+        if data is not None:
+            if key in data:
+                del data[key]
+                JsonUtil.write_json(data, file_path, encoding)
+            else:
+                print(f"键 '{key}' 不存在。")

+ 21 - 0
wtoaamapi/apps/business/algorithm/utils/test.py

@@ -0,0 +1,21 @@
+import pandas as pd
+import numpy as np
+
+# 创建一个示例DataFrame
+df = pd.DataFrame({
+    'A': [1, np.nan, 3],
+    'B': [np.nan, np.nan, np.nan],
+    'C': [2, 2, np.nan],
+    'D': [np.nan, 3, 3]
+})
+
+# 指定你想要检查的列
+columns_to_check = ['A', 'B', 'C']
+
+# 检查指定列中非全为空的列
+non_empty_columns = df[columns_to_check].apply(lambda x: x.notnull().any(), axis=0)
+
+# 获取非全为空的列名
+non_empty_column_names = non_empty_columns[non_empty_columns].index.tolist()
+
+print("非全为空的列名:", non_empty_column_names)

+ 6 - 11
dataAnalysisBusiness/algorithm/windRoseOfTurbine.py → wtoaamapi/apps/business/algorithm/windRoseOfTurbine.py

@@ -7,9 +7,9 @@ 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 .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import *
 from matplotlib.cm import get_cmap 
 from matplotlib.colors import ListedColormap   
 
@@ -25,12 +25,7 @@ class WinRoseOfTurbineAnalyst(Analyst):
     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}")
-        
+    def windRoseAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
         # 风速区间  
         bins = [0, 3, 6, 9, np.inf]  
         speed_labels = ['[0,3)', '[3,6)', '[6,9)', '>=9']  
@@ -43,7 +38,7 @@ class WinRoseOfTurbineAnalyst(Analyst):
         # 按设备名分组数据
         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)  
@@ -65,7 +60,7 @@ class WinRoseOfTurbineAnalyst(Analyst):
                 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_title("Wind Rose", va='top')  
             ax.set_theta_zero_location('N')  # 设置0度位置为北  
             ax.set_theta_direction(-1)  # 设置角度方向为顺时针  
             ax.set_yticklabels([])  # 不显示y轴刻度标签  

+ 176 - 0
wtoaamapi/apps/business/algorithm/yawErrorAnalyst.py

@@ -0,0 +1,176 @@
+import os
+import numpy as np
+from plotly.subplots import make_subplots
+import plotly.graph_objects as go
+from scipy.optimize import curve_fit
+import matplotlib.pyplot as plt
+import pandas as pd
+from .analyst import Analyst
+from .utils.directoryUtil import DirectoryUtil as dir
+from confBusiness import ConfBusiness
+
+
+class YawErrorAnalyst(Analyst):
+    """
+    风电机组静态偏航误差分析
+    """
+
+    def typeAnalyst(self):
+        return "yaw_error"
+
+    def turbineAnalysis(self,
+                        dataFrame,
+                        outputAnalysisDir,
+                        outputFilePath,
+                        confData: ConfBusiness,
+                        turbineName):
+
+        self.yaw_error(dataFrame, outputFilePath,
+                       confData.field_turbine_time, confData.field_angle_included, confData.field_power, confData.field_pitch_angle1)
+
+    def yaw_error(self, dataFrame, output_path,  field_time, field_angle_included_wind_dir, field_power_active, pitch_col):
+        # Calculate floor values and other transformations
+        dataFrame['wind_dir_floor'] = np.floor(dataFrame[field_angle_included_wind_dir]).astype(int)
+        # dataFrame['wind_dir_floor'] = dataFrame[field_angle_included_wind_dir].round().astype(int)
+
+        dataFrame['power'] = dataFrame[field_power_active].astype(float)
+        dataFrame['time'] = pd.to_datetime(dataFrame[field_time])
+
+        # Calculate aggregated metrics for power
+        grouped = dataFrame.groupby('wind_dir_floor').agg({
+            'power': ['mean', 'max', 'min', 'median', lambda x: (x > 0).sum(), lambda x: (x > x.median()).sum()]
+        }).reset_index()
+
+        # Rename columns for clarity
+        grouped.columns = ['wind_dir_floor', 'mean_power', 'max_power',
+                           'min_power', 'median_power', 'power_gt_0', 'power_gt_80p']
+
+        # Calculate total sums for conditions
+        power_gt_0_sum = grouped['power_gt_0'].sum()
+        power_gt_80p_sum = grouped['power_gt_80p'].sum()
+
+        # Calculate ratios
+        grouped['ratio_0'] = grouped['power_gt_0'] / power_gt_0_sum
+        grouped['ratio_80p'] = grouped['power_gt_80p'] / power_gt_80p_sum
+
+        # Filter out zero ratios and calculate slope
+        grouped = grouped[grouped['ratio_0'] > 0]
+        grouped['slop'] = grouped['ratio_80p'] / grouped['ratio_0']
+
+        # Sort by wind direction floor
+        grouped.sort_values('wind_dir_floor', inplace=True)
+
+        # Write to CSV
+        grouped.to_csv(output_path, index=False)
+
+    def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
+        self.yaw_result(outputAnalysisDir)
+
+    def poly_func(self, x, a, b, c, d, e):
+        return a * x**4 + b * x ** 3 + c * x ** 2 + d * x + e
+
+    def yaw_result(self, csvFileDir):
+        files = os.listdir(csvFileDir)
+        for file in files:
+            old_name = os.path.join(csvFileDir, file)
+            print(file)
+            # if os.path.isdir(old_name):
+            #    data_stat(old_name)
+            #    continue
+            # print(old_name)
+            if not file.endswith(".csv"):
+                continue
+            data_path = old_name
+            # limit_path = data_path+".limit"
+
+            df = pd.read_csv(data_path)
+            
+            # 不具备普适性,视静态偏航误差输出数据结果调整,经2024-3-13验证输出结果不可信
+            # df = df[df["wind_dir_floor"] > -12]
+            # df = df[df["wind_dir_floor"] < 12]
+
+            df.dropna(inplace=True)
+            # drop extreme value, the 3 largest and 3 smallest
+
+            df_ = df[df["ratio_80p"] > 0.000]
+
+            xdata = df_['wind_dir_floor']
+            ydata = df_['slop']
+
+            # make ydata smooth
+            ydata = ydata.rolling(7).median()[6:]
+            xdata = xdata[3:-3]
+            
+            # 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()
+
+            # subplots
+            plt.figure(figsize=(10, 6))
+            fig = plt.subplot(211)
+            plt.title(file)
+            plt.scatter(x=df["wind_dir_floor"], y=df["slop"],
+                        s=5, label="energy gain")
+            plt.plot(xdata, fitted_ydata, 'r-', label="fit")
+            plt.scatter(xdata[max_pos], fitted_ydata[max_pos], s=20,
+                        label="max pos:"+str(df["wind_dir_floor"][max_pos]))
+            plt.legend()
+            plt.grid(True)
+            fig = plt.subplot(212)
+            plt.scatter(x=df["wind_dir_floor"],
+                        y=df["ratio_0"], s=5, label="base energy")
+            plt.scatter(x=df["wind_dir_floor"],
+                        y=df["ratio_80p"], s=5, label="slope energy")
+            plt.legend()
+            plt.grid(True)
+            plt.savefig(data_path+".png")
+            plt.close()
+            # calc squear error of fitted_ydata and ydata
+
+            print(file, df["wind_dir_floor"][max_pos])
+
+    # def yaw_result(self, csvFileDir):
+    #     files = os.listdir(csvFileDir)
+    #     for file in files:
+    #         if not file.endswith(".csv"):
+    #             continue
+
+    #         data_path = os.path.join(csvFileDir, file)
+    #         df = pd.read_csv(data_path)
+    #         df.dropna(inplace=True)
+
+    #         # Filter and smooth data
+    #         df_ = df[df["ratio_80p"] > 0.000]
+    #         xdata = df_['wind_dir_floor']
+    #         ydata = df_['slop'].rolling(7).median()[6:]
+    #         xdata = xdata[3:-3].reset_index(drop=True)
+    #         ydata = ydata.reset_index(drop=True)
+
+    #         # Curve fitting
+    #         popt, _ = curve_fit(self.poly_func, xdata, ydata)
+    #         fitted_ydata = self.poly_func(xdata, *popt)
+    #         max_pos = fitted_ydata.argmax()
+
+    #         # Plotting
+    #         fig = make_subplots(rows=2, cols=1)
+
+    #         # Scatter plot of energy gain
+    #         fig.add_trace(go.Scatter(x=df["wind_dir_floor"], y=df["slop"], mode='markers', name="energy gain", marker=dict(size=5)), row=1, col=1)
+    #         # Line plot of fit
+    #         fig.add_trace(go.Scatter(x=xdata, y=fitted_ydata, mode='lines', name="fit", line=dict(color='red')), row=1, col=1)
+    #         # Marker for max position
+    #         fig.add_trace(go.Scatter(x=[xdata[max_pos]], y=[fitted_ydata[max_pos]], mode='markers', name=f"max pos: {xdata[max_pos]}", marker=dict(size=10, color='orange')), row=1, col=1)
+
+    #         # Scatter plot of base and slope energy
+    #         fig.add_trace(go.Scatter(x=df["wind_dir_floor"], y=df["ratio_0"], mode='markers', name="base energy", marker=dict(size=5)), row=2, col=1)
+    #         fig.add_trace(go.Scatter(x=df["wind_dir_floor"], y=df["ratio_80p"], mode='markers', name="slope energy", marker=dict(size=5)), row=2, col=1)
+
+    #         fig.update_layout(height=600, width=800, title_text=file)
+    #         fig.write_image(data_path+".png")
+
+    #         print(file, xdata[max_pos])

+ 24 - 59
dataContract/algorithmContract/confBusiness.py → wtoaamapi/apps/business/confBusiness.py

@@ -1,33 +1,13 @@
 import pandas as pd
-from utils.jsonUtil import JsonUtil
+from algorithm.utils.jsonUtil.jsonUtil import JsonUtil
 
 # 全局变量
 charset_unify = 'utf-8'
-CSVSuffix = '.csv'
 
 Field_NameOfTurbine = "turbine_name"
 Field_GeneratorTorque = "generator_torque"
 Field_GeneratorSpeed = "generator_speed"
-Field_RotorSpeed = "rotor_speed"
-Field_WindSpeed = "wind_speed"
-Field_AngleIncluded = "angle_included"
-Field_YearMonth = "year-month"
-Field_YearMonthDay = "year-month-day"
-Field_PowerFloor= "power_floor"
-Field_Cp = "cp"
-Field_CpMedian = "cp_median"
-Field_TSR = "tsr"
-Field_TSRMedian = "tsr_median"
-Field_YawError="yaw_error"
-Field_LableFlag="lab"
-
-
-class GraphSet:
-    def __init__(self):
-        self.multipl = None
-        self.step = None
-        self.min = None
-        self.max = None
+Field_AngleIncluded="angle_included"
 
 
 class ConfBusiness:
@@ -63,7 +43,7 @@ class ConfBusiness:
         # 将字符串转换为 pd.Timestamp 类型
         self.start_time = None
         self.end_time = None
-        self.excludingMonths = None  # 排除指定的月份数据 格式%Y-%m
+        self.excludingMonths=None # 排除指定的月份数据 格式%Y-%m
 
         self.field_turbine_time = None    # 字段名 时间
         self.field_turbine_name = None    # 字段名 机组名
@@ -75,8 +55,8 @@ class ConfBusiness:
         self.field_pitch_angle3 = None    # 字段名 桨距角3
         self.field_turbine_state = None   # 字段名 风机状态
         self.field_gen_speed = None       # 字段名 发电机转速
-        self.value_gen_speed_multiple = None  # 值 发电机转速放大倍数
-        self.value_gen_speed_step = None            # 值 发电机转速轴系间隔
+        self.value_gen_speed_multiple=None  # 值 发电机转速放大倍数
+        self.value_gen_speed_step=None            # 值 发电机转速轴系间隔
         self.value_gen_speed_min = None       # 值 发电机转速最小
         self.value_gen_speed_max = None       # 值 发电机转速最大
         self.field_rotor_speed = None     # 字段名 叶轮转速
@@ -87,14 +67,8 @@ class ConfBusiness:
         self.field_env_temp = None        # 字段名 环境温度
         self.field_nacelle_temp = None    # 字段名 机舱温度
         self.field_temperature_large_components = None  # 字段名列表  大部件温度传感器
-        self.temperature_Generator = None
-        self.graphSets = None
-        self.field_activePowerSet = None
-        self.field_activePowerAvailable = None
-        self.rated_cut_in_windspeed = None  #额定切入风速
-        self.rated_cut_out_windspeed = None #额定切出风速
-
-    def loadConfig(self, jsonFilePath, charset=charset_unify):
+
+    def loadConfig(self,jsonFilePath, charset=charset_unify):
         """
         配置初始化
         """
@@ -103,13 +77,10 @@ class ConfBusiness:
 
         # 将配置数据存储在变量中
         configData = JsonUtil.read_json(jsonFilePath)
-
+                
         self.farm_name = configData['name_PowerFarm']
         self.rated_power = configData['rated_Power_Turbine_Unit_kW']
         self.rated_WindSpeed = configData["rated_WindSpeed"]
-        self.rated_cut_in_windspeed = configData["rated_cut_in_windspeed"]  #额定切入风速
-        self.rated_cut_out_windspeed = configData["rated_cut_out_windspeed"]#额定切出风速
-
         self.rotor_diameter = configData['rotor_diameter']
         self.rotational_Speed_Ratio = configData['rotational_Speed_Ratio']
         self.density_air = configData['density_air']
@@ -143,7 +114,7 @@ class ConfBusiness:
             self.start_time_str, format='%Y-%m-%d %H:%M:%S')
         self.end_time = pd.to_datetime(
             self.end_time_str, format='%Y-%m-%d %H:%M:%S')
-        self.excludingMonths = configData['excludingMonths']
+        self.excludingMonths= configData['excludingMonths']
 
         self.field_turbine_time = configData['turbine_Time']
         self.field_turbine_name = configData['turbine_Name']
@@ -155,10 +126,10 @@ class ConfBusiness:
         self.field_pitch_angle3 = configData['pitch_Angle3']
         self.field_turbine_state = configData['state_Turbine']
         self.field_gen_speed = configData['speed_Generator']
-        # self.value_gen_speed_multiple = configData['speed_Generator_Multiple']
-        # self.value_gen_speed_step = configData['speed_Generator_Step']
-        # self.value_gen_speed_min = configData['speed_Generato_min']
-        # self.value_gen_speed_max = configData['speed_Generato_max']
+        self.value_gen_speed_multiple=configData['speed_Generator_Multiple']
+        self.value_gen_speed_step=configData['speed_Generator_Step']
+        self.value_gen_speed_min = configData['speed_Generato_min']
+        self.value_gen_speed_max = configData['speed_Generato_max']
         self.field_rotor_speed = configData['speed_Rotor']
         self.field_torque = configData['torque']
         self.field_wind_dir = configData['direction_Wind']
@@ -167,22 +138,16 @@ class ConfBusiness:
         self.field_env_temp = configData['temperature_Env']
         self.field_nacelle_temp = configData['temperature_Nacelle']
         self.field_temperature_large_components = configData['temperature_large_components']
-        self.temperature_Generator = configData["temperature_Generator"]
-        self.graphSets = configData["graphSets"]
-        self.field_Cabin_Vibrate_X = configData["Cabin_Vibrate_X"]
-        self.field_Cabin_Vibrate_Y = configData["Cabin_Vibrate_Y"]
-        self.field_activePowerSet = configData["activePowerSet"]
-        self.field_activePowerAvailable = configData["activePowerAvailable"]
 
         return self
-
-    # def add_W_if_starts_with_digit(self,s):
-    #     if s and s[0].isdigit():
-    #         return 'W' + s
-    #     return s
-
-    # 定义一个函数,用于检查字符串首字母是否为数字,并在是的情况下添加'W'
-    def add_W_if_starts_with_digit(self, s):
-        if isinstance(s, str) and s[0].isdigit():
-            return 'W' + s
-        return s
+        
+    # def add_W_if_starts_with_digit(self,s):  
+    #     if s and s[0].isdigit():  
+    #         return 'W' + s  
+    #     return s  
+    
+    # 定义一个函数,用于检查字符串首字母是否为数字,并在是的情况下添加'W'  
+    def add_W_if_starts_with_digit(self,s):  
+        if isinstance(s, str) and s[0].isdigit():  
+            return 'W' + s  
+        return s  

+ 22 - 10
wtoaamapi/apps/business/main.py

@@ -1,15 +1,15 @@
 import pandas as pd
-import importlib
+from algorithm.utils.jsonUtil.jsonUtil import JsonUtil
 import concurrent.futures
-from utils.jsonUtil import JsonUtil
-from algorithmContract.confBusiness import ConfBusiness
+import confBusiness
+import importlib
 from algorithm.dataProcessor import DataProcessor
-from behavior.baseAnalyst import BaseAnalyst
-from behavior.analyst import Analyst
+from algorithm.baseAnalyst import BaseAnalyst
+from algorithm.analyst import Analyst
 import seaborn as sns
 sns.set_style("darkgrid") 
 
-def buildDynamicInstance(module_path, class_name, confData: ConfBusiness):
+def buildDynamicInstance(module_path, class_name, confData: confBusiness.ConfBusiness):
     # 动态导入模块
     module = importlib.import_module(module_path)
     # 获取类
@@ -19,6 +19,7 @@ def buildDynamicInstance(module_path, class_name, confData: ConfBusiness):
 
     return instance
 
+
 def dynamic_instance_and_call(module_path, class_name, method_name, *args, **kwargs):
     # 动态导入模块
     module = importlib.import_module(module_path)
@@ -42,7 +43,7 @@ def loadJson(filePath):
 def executeAnalysis(config):
     configBusinessFilePath=config["configFilePath"]
     print(configBusinessFilePath)
-    businessConfig=ConfBusiness()
+    businessConfig=confBusiness.ConfBusiness()
     configBusiness = businessConfig.loadConfig(configBusinessFilePath)
 
     baseAnalysts=[]
@@ -54,15 +55,26 @@ def executeAnalysis(config):
         className = dynamicAnalyst["className"]
         methodName = dynamicAnalyst["methodName"]
         analyst = buildDynamicInstance(package, className, configBusiness)
-
-        analysts.append(analyst)
                    
+        if not isinstance(analyst,Analyst) and  isinstance(analyst ,BaseAnalyst):
+            baseAnalysts.append(analyst)
+        else : 
+            if isinstance(analyst,Analyst):
+                analysts.append(analyst)
+
     process = DataProcessor()
 
+    for analyst in baseAnalysts:
+        process.attachBaseAnalyst(analyst)
+
     for analyst in analysts:
         process.attach(analyst)
+
     process.execute(configBusiness)
 
+    for analyst in baseAnalysts:
+        process.detachBaseAnalyst(analyst)
+
     for analyst in analysts:
         process.detach(analyst)
 
@@ -74,7 +86,7 @@ if __name__ == "__main__":
         executeAnalysis(config)
 
     # 使用多线程时,matplotlib绘图报错
-    # 在使用Python语言的matplotlib包时,如果尝试在多线程环境中创建图形界面,你可能会遇到“QWidget-: Must construct a QApplication before a QWidget”的错误。
+    # 在使用Python语言的matplotlib包时,如果尝试在多线程环境中创建图形界面,你可能会遇到“QWidget: Must construct a QApplication before a QWidget”的错误。
     # 这个错误通常是因为matplotlib的图形后端(backend)依赖于Qt框架,而Qt框架要求在一个QApplication实例被创建之后再创建任何QWidget对象。
     # 在多线程环境中,每个线程都应该有一个它自己的事件循环。然而,QApplication实例通常是全局的,并且应该只在主线程中创建一次。
     # 如果你尝试在一个非主线程中创建QApplication或者QWidget,就会遇到这个问题。

+ 25 - 50
wtoaamapi/apps/business/test.py

@@ -1,50 +1,25 @@
-import pandas as pd  
-import plotly.graph_objects as go  
-from plotly.subplots import make_subplots  
-  
-# 假设你的DataFrame叫做df,并且已经包含了所需字段  
-# 如果你的数据是CSV文件,可以使用pd.read_csv('your_file.csv')来加载数据  
-# df = pd.read_csv('your_file.csv')  
-  
-# 示例数据  
-data = {  
-    '机组名': ['机组A', '机组B', '机组C', '机组D'],  
-    '时间': ['2024-01-09 09:13:29', '2024-01-10 10:14:30', '2024-02-09 08:13:29', '2024-02-10 09:14:30'],  
-    '年月': ['2024-01', '2024-01', '2024-02', '2024-02'],  
-    '风速': [5.0, 6.0, 4.5, 5.5],  
-    '有功功率': [1000, 1200, 900, 1100]  
-}  
-  
-df = pd.DataFrame(data)  
-  
-# 创建颜色映射,将每个年月映射到一个唯一的颜色  
-unique_months = df['年月'].unique()  
-colors = [f'rgb({i}, {150 - i}, 50)' for i in range(len(unique_months))]  
-color_map = dict(zip(unique_months, colors))  
-  
-# 使用make_subplots创建3D散点图  
-fig = make_subplots(rows=1, cols=1, specs=[[{"type": "scatter3d"}]])  
-  
-# 遍历DataFrame的每一行,为每个点添加数据  
-for index, row in df.iterrows():  
-    x = row['风速']  
-    y = row['年月']  
-    z = row['有功功率']  
-    color = color_map[y]  
-      
-    # 添加散点到子图  
-    fig.add_trace(go.Scatter3d(x=[x], y=[y], z=[z], mode='markers', marker=dict(color=color)), row=1, col=1)  
-  
-# 更新子图的布局,设置y轴为category类型,并设置其类别顺序  
-fig.update_layout(  
-    title='3D散点图:风速、年月与有功功率',  
-    margin=dict(l=0, r=0, b=0, t=0),  
-    scene=dict(  
-        xaxis=dict(title='风速'),  
-        yaxis=dict(title='年月', tickmode='array', tickvals=unique_months, ticktext=unique_months, categoryorder='category ascending'),  
-        zaxis=dict(title='有功功率')  
-    )  
-)  
-  
-# 显示图形  
-fig.show()
+import pandas as pd
+import numpy as np
+import plotly.express as px
+
+# 示例DataFrame
+df = pd.DataFrame({
+    'wind_speed': np.random.uniform(3, 25, 10),  # 生成100个3到25之间的随机风速值
+    'wind_direction': np.random.uniform(0, 360, 10),  # 生成100个0到360之间的随机风向值
+})
+
+# 将风向划分为16个区间
+df['wind_direction_bin'] = pd.cut(df['wind_direction'], bins=np.linspace(0, 360, 16), labels=False, right=False)
+
+# 将风速按范围[3, 25)划分为区间,每3m/s为一个步长
+df['wind_speed_bin'] = pd.cut(df['wind_speed'], bins=np.arange(3, 26, 3))
+
+# 计算每个风向和风速区间的频率
+wind_rose_data = df.groupby(['wind_direction_bin', 'wind_speed_bin']).size().reset_index(name='frequency')
+
+# 绘制风玫瑰图
+fig = px.bar_polar(wind_rose_data, r='frequency', theta='wind_direction_bin',
+                   color='wind_speed_bin', template='plotly_dark',
+                   color_discrete_sequence=px.colors.sequential.Plasma_r)
+
+fig.show()

+ 1 - 1
dataAnalysisBehavior/common/turbineInfo.py → wtoaamapi/apps/business/turbineInfo.py

@@ -1,4 +1,4 @@
-from algorithmContract.confBusiness import charset_unify,Field_NameOfTurbine,ConfBusiness
+from confBusiness import charset_unify,Field_NameOfTurbine,ConfBusiness
 import pandas as pd
 
 

+ 1 - 0
wtoaamapi/apps/viewDemo/viewUser.py

@@ -10,6 +10,7 @@ tags = ['user']
  
 class User(ViewSet):
     @swagger_auto_schema(
+        tags=['Demo'],
         operation_description="apiview post description override",
         request_body=openapi.Schema(
             type=openapi.TYPE_OBJECT,

+ 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)