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
 风力发电机组运行分析算法模型 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',
     version='1.0.202403180918',
     description='Data Analysis Business Package', # 描述信息
     description='Data Analysis Business Package', # 描述信息
     author='Xie Zhou Yang', # 作者
     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
 from setuptools import setup, find_packages
 
 
 setup(
 setup(
-    name='repositoryZN',
+    name='repository',
     version='1.0.202403180918',
     version='1.0.202403180918',
     description='Repository Package', # 描述信息
     description='Repository Package', # 描述信息
     author='Xie Zhou Yang', # 作者
     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 os
 import pandas as pd
 import pandas as pd
 import numpy as np
 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):
 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
 from plotly.subplots import make_subplots
 import seaborn as sns
 import seaborn as sns
 import matplotlib.pyplot as plt
 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(
         print('rotor_diameter={}  air_density={}'.format(
             rotor_diameter, air_density))
             rotor_diameter, air_density))
 
 
-        dataFrame = dataFrame.dropna(subset=[power_col])
-
         dataFrame['power'] = dataFrame[power_col]  # Alias the power column
         dataFrame['power'] = dataFrame[power_col]  # Alias the power column
         # Floor division by 10 and then multiply by 10
         # 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['wind_speed'] = dataFrame[wind_speed_col].astype(float)
         dataFrame['rotor_speed'] = dataFrame[field_rotor_speed].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['power'] = pd.to_numeric(dataFrame['power'], errors='coerce')
         dataFrame['wind_speed'] = pd.to_numeric(
         dataFrame['wind_speed'] = pd.to_numeric(
             dataFrame['wind_speed'], errors='coerce')
             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
         # Group by wind_speed_floor and calculate mean, max, and min of the specified columns
         grouped = dataFrame.groupby('wind_speed_floor').agg(
         grouped = dataFrame.groupby('wind_speed_floor').agg(
@@ -74,9 +71,9 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         grouped.to_csv(output_path, index=False)
         grouped.to_csv(output_path, index=False)
 
 
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
     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.
         Generates Cp distribution plots for turbines in a wind farm.
 
 
@@ -87,11 +84,11 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         """
         """
 
 
         output_path = csvFileDirOfCp
         output_path = csvFileDirOfCp
-        value_step = 0.5
 
 
         field_Name_Turbine = "turbine_name"
         field_Name_Turbine = "turbine_name"
         x_name = 'wind_speed_floor'
         x_name = 'wind_speed_floor'
         y_name = 'cp'
         y_name = 'cp'
+        split_way = '_cp_windspeed.csv'
 
 
         sns.set_palette('deep')
         sns.set_palette('deep')
         res = pd.DataFrame()
         res = pd.DataFrame()
@@ -99,13 +96,13 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
             for file_name in file_names:
             for file_name in file_names:
 
 
-                if not file_name.endswith(CSVSuffix):
+                if not file_name.endswith(".csv"):
                     continue
                     continue
 
 
                 file_path = os.path.join(root, file_name)
                 file_path = os.path.join(root, file_name)
                 print(file_path)
                 print(file_path)
                 frame = pd.read_csv(file_path, encoding=encoding)
                 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
                 frame[field_Name_Turbine] = turbine_name
 
 
                 res = pd.concat(
                 res = pd.concat(
@@ -113,21 +110,13 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
 
 
         ress = res.reset_index()
         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,
         ax = sns.lineplot(x=x_name, y=y_name, data=ress,
                           hue=field_Name_Turbine)
                           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')
         ax.set_title('Cp-Distribution')
-        plt.xticks(rotation=45)  # 旋转45度
         plt.legend(ncol=4)
         plt.legend(ncol=4)
         plt.savefig(os.path.join(
         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()
         plt.close()
 
 
         grouped = ress.groupby(field_Name_Turbine)
         grouped = ress.groupby(field_Name_Turbine)
@@ -138,16 +127,7 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
                               palette=sns.color_palette(color), legend=False)
                               palette=sns.color_palette(color), legend=False)
             ax = sns.lineplot(x=x_name, y=y_name, data=group,
             ax = sns.lineplot(x=x_name, y=y_name, data=group,
                               color='darkblue', legend=False)
                               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))
             ax.set_title('turbine name={}'.format(name))
-            plt.xticks(rotation=45)  # 旋转45度
             plt.savefig(os.path.join(output_path, "{}.png".format(
             plt.savefig(os.path.join(output_path, "{}.png".format(
                 name)), bbox_inches='tight', dpi=120)
                 name)), bbox_inches='tight', dpi=120)
             plt.close()
             plt.close()
@@ -156,6 +136,7 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
         field_Name_Turbine = "设备名"
         field_Name_Turbine = "设备名"
         x_name = 'wind_speed_floor'
         x_name = 'wind_speed_floor'
         y_name = 'cp'
         y_name = 'cp'
+        split_way = '_cp.csv'
         # Create the output path based on the farm name
         # Create the output path based on the farm name
         output_path = csvFileDir  # output_path_template.format(farm_name)
         output_path = csvFileDir  # output_path_template.format(farm_name)
         # Ensure the output directory exists
         # Ensure the output directory exists
@@ -169,7 +150,7 @@ class CpWindSpeedAnalyst(AnalystExcludeRatedPower):
             for file_name in file_names:
             for file_name in file_names:
                 full_path = os.path.join(root, file_name)
                 full_path = os.path.join(root, file_name)
                 frame = pd.read_csv(full_path, encoding='gbk')
                 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))
                 print("turbine_name={}".format(turbine_name))
                 frame[field_Name_Turbine] = turbine_name
                 frame[field_Name_Turbine] = turbine_name
                 res = pd.concat(
                 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
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
 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
 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
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
 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 calendar
 import random 
 import random 
 
 
 
 
-class DataIntegrityOfSecondAnalyst(Analyst):
+class DataIntegrityOfSecondAnalyst(BaseAnalyst):
     """
     """
     风电机组秒级数据完整度分析
     风电机组秒级数据完整度分析
     """
     """
 
 
     def typeAnalyst(self):
     def typeAnalyst(self):
         return "data_integrity_second"
         return "data_integrity_second"
-    
-    def filterCommon(self,dataFrame:pd.DataFrame, confData:ConfBusiness):        
-        return dataFrame
 
 
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
         groupedDataFrame = self.dataIntegrityByMonth(
         groupedDataFrame = self.dataIntegrityByMonth(
@@ -42,16 +39,16 @@ class DataIntegrityOfSecondAnalyst(Analyst):
     def fullMonthIndex(self,start_time,end_time,turbine_name,new_frame):
     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
         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_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()
         plot_res = pd.DataFrame()
         grouped = new_frame.groupby(turbine_name)
         grouped = new_frame.groupby(turbine_name)
         for name,group in grouped:
         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['数据完整度%'] = group['数据完整度%'].fillna(0)
             group[turbine_name] = name
             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)
             plot_res = pd.concat([plot_res,group],axis=0,sort=False)
 
 
         return plot_res
         return plot_res
@@ -72,7 +69,7 @@ class DataIntegrityOfSecondAnalyst(Analyst):
         new_frame = new_frame.reset_index()
         new_frame = new_frame.reset_index()
         new_frame['month'] = new_frame['month'].astype(
         new_frame['month'] = new_frame['month'].astype(
             str).apply(lambda x: x.zfill(2))
             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)
             str) + '-' + new_frame['month'].astype(str)
         
         
         new_frame = self.fullMonthIndex(confData.start_time,confData.end_time,fieldTurbineName,new_frame)
         new_frame = self.fullMonthIndex(confData.start_time,confData.end_time,fieldTurbineName,new_frame)
@@ -83,9 +80,9 @@ class DataIntegrityOfSecondAnalyst(Analyst):
         title = 'time integrity check(%)'
         title = 'time integrity check(%)'
         fig, ax = plt.subplots(figsize=(18, 15), dpi=300)
         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,
             result = pd.pivot(groupedDataFrame, index=fieldTurbineName,
-                              columns=Field_YearMonth, values="数据完整度%")
+                              columns='year-month', values="数据完整度%")
             ax = sns.heatmap(data=result, square=True, annot=True,
             ax = sns.heatmap(data=result, square=True, annot=True,
                              linewidths=0.3, cbar=False, fmt='g',)
                              linewidths=0.3, cbar=False, fmt='g',)
             bottom, top = ax.get_ylim()
             bottom, top = ax.get_ylim()
@@ -97,7 +94,7 @@ class DataIntegrityOfSecondAnalyst(Analyst):
                         r'/{}数据完整度分析.png'.format(farmName), bbox_inches='tight')
                         r'/{}数据完整度分析.png'.format(farmName), bbox_inches='tight')
             plt.close()
             plt.close()
         else:
         else:
-            result = pd.pivot(groupedDataFrame, index=Field_YearMonth,
+            result = pd.pivot(groupedDataFrame, index='year-month',
                               columns=fieldTurbineName, values="数据完整度%")
                               columns=fieldTurbineName, values="数据完整度%")
             ax = sns.heatmap(data=result, square=True, annot=True,
             ax = sns.heatmap(data=result, square=True, annot=True,
                              linewidths=0.3, cbar=False, fmt='g',)
                              linewidths=0.3, cbar=False, fmt='g',)
@@ -114,9 +111,9 @@ class DataIntegrityOfSecondAnalyst(Analyst):
 
 
     def draw(self, groupedDataFrame, outputAnalysisDir, farmName, fieldTurbineName):
     def draw(self, groupedDataFrame, outputAnalysisDir, farmName, fieldTurbineName):
         fig = make_subplots(rows=1, cols=1)
         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(
             result = groupedDataFrame.pivot(
-                index=fieldTurbineName, columns=Field_YearMonth, values="数据完整度%")
+                index=fieldTurbineName, columns='year-month', values="数据完整度%")
             fig.add_trace(
             fig.add_trace(
                 go.Heatmap(
                 go.Heatmap(
                     z=result.values,
                     z=result.values,
@@ -136,7 +133,7 @@ class DataIntegrityOfSecondAnalyst(Analyst):
             )
             )
         else:
         else:
             result = groupedDataFrame.pivot(
             result = groupedDataFrame.pivot(
-                index=Field_YearMonth, columns=fieldTurbineName, values="数据完整度%")
+                index='year-month', columns=fieldTurbineName, values="数据完整度%")
             fig.add_trace(
             fig.add_trace(
                 go.Heatmap(
                 go.Heatmap(
                     z=result.values,
                     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
     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
 A_example = calculate_area(diameter_example)
 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 seaborn as sns
 import matplotlib.pyplot as plt
 import matplotlib.pyplot as plt
 from matplotlib.ticker import MultipleLocator
 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):
 class PitchGeneratorSpeedAnalyst(Analyst):
@@ -31,11 +31,10 @@ class PitchGeneratorSpeedAnalyst(Analyst):
         sns.set_palette('deep')
         sns.set_palette('deep')
         # 遍历每个设备并绘制散点图
         # 遍历每个设备并绘制散点图
         for name, group in grouped:
         for name, group in grouped:
-            groupNew = group.copy()
+            groupNew=group.copy()
 
 
             if not self.common.isNone(confData.value_gen_speed_multiple):
             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}时,会出现颜色丢失问题;
             # sns.lmplot函数参数scatter_kws: 设置为{"s": 5}时,会出现颜色丢失问题;
             g = sns.lmplot(x=fieldGeneratorSpeed, y=fieldPitchAngle, data=groupNew,
             g = sns.lmplot(x=fieldGeneratorSpeed, y=fieldPitchAngle, data=groupNew,
@@ -45,24 +44,21 @@ class PitchGeneratorSpeedAnalyst(Analyst):
 
 
             # 设置x轴和y轴的刻度
             # 设置x轴和y轴的刻度
             for ax in g.axes.flat:
             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_xlabel(x_name)
                 ax.set_ylabel(y_name)
                 ax.set_ylabel(y_name)
 
 
-            # 设置x轴刻度值旋转角度为45度
+            # 设置x轴刻度值旋转角度为45度  
             plt.tick_params(axis='x', rotation=45)
             plt.tick_params(axis='x', rotation=45)
             # 调整布局和设置标题
             # 调整布局和设置标题
             plt.tight_layout()
             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
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
 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):
 class PowerCurveAnalyst(Analyst):
     """
     """
@@ -23,15 +23,22 @@ class PowerCurveAnalyst(Analyst):
 
 
     def typeAnalyst(self):
     def typeAnalyst(self):
         return "power_curve"
         return "power_curve"
-    
+
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
         if len(dataFrameMerge)<=0:
         if len(dataFrameMerge)<=0:
             print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
             print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
             return
             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):
     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.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_xlabel(x_name)  
             ax.set_ylabel(y_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')  
             ax.legend(loc='lower right')  
@@ -116,9 +114,12 @@ class PowerCurveAnalyst(Analyst):
         confData (ConfBusiness): 配置 
         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
         # 初始化结果DataFrame
         all_res = pd.DataFrame()
         all_res = pd.DataFrame()
 
 
@@ -136,12 +137,12 @@ class PowerCurveAnalyst(Analyst):
         ress = all_res.reset_index(drop=True)        
         ress = all_res.reset_index(drop=True)        
         
         
         self.plot_power_curve(ress,  outputAnalysisDir, dataFrameGuaranteePowerCurve,Field_NameOfTurbine,
         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)
         grouped=ress.groupby(Field_NameOfTurbine)
         for name, group in grouped:
         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):
     def power_curve_helper(self, group, wind_speed_col, power_col, bins):
         """  
         """  
@@ -157,7 +158,7 @@ class PowerCurveAnalyst(Analyst):
         act_line.columns = ['风速区间', '有效数量', '实际功率曲线']
         act_line.columns = ['风速区间', '有效数量', '实际功率曲线']
         return act_line
         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_xlabel('wind speed')
         ax.set_ylabel('power')
         ax.set_ylabel('power')
         ax.set_title('power curve')
         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.legend(title='turbine',bbox_to_anchor=(1.02, 0.5),ncol=2, loc='center left', borderaxespad=0.) 
         plt.xticks(rotation=45)  # 旋转45度
         plt.xticks(rotation=45)  # 旋转45度
         plt.savefig(os.path.join(output_path, filename),
         plt.savefig(os.path.join(output_path, filename),
                     bbox_inches='tight', dpi=120)
                     bbox_inches='tight', dpi=120)
         plt.close()
         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())
         color = ["lightgrey"]*len(ress[Field_NameOfTurbine].unique())
         fig, ax = plt.subplots(figsize=(8, 8))
         fig, ax = plt.subplots(figsize=(8, 8))
@@ -204,20 +194,6 @@ class PowerCurveAnalyst(Analyst):
         ax.set_xlabel('wind speed')
         ax.set_xlabel('wind speed')
         ax.set_ylabel('power')
         ax.set_ylabel('power')
         ax.set_title('turbine_name={}'.format(turbineName))
         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.xticks(rotation=45)  # 旋转45度
         plt.savefig(outputAnalysisDir + r"/{}-curve.png".format(turbineName),
         plt.savefig(outputAnalysisDir + r"/{}-curve.png".format(turbineName),
                     bbox_inches='tight', dpi=120)
                     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 numpy as np
 import matplotlib.pyplot as plt  
 import matplotlib.pyplot as plt  
 import seaborn as sns  
 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):
 class PowerOscillationAnalyst(Analyst):
     """
     """
@@ -57,10 +57,10 @@ class PowerOscillationAnalyst(Analyst):
         - farm_name: str, name of the wind farm.
         - farm_name: str, name of the wind farm.
         - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
         - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
         """        
         """        
-        sns.set_palette('deep')
         field_Name_Turbine = "turbine_name"
         field_Name_Turbine = "turbine_name"
         x_name = 'power_col_floor'  
         x_name = 'power_col_floor'  
         y_name = 'speed_diff'  
         y_name = 'speed_diff'  
+        split_way = '_power_diff.csv' 
 
 
         # 初始化结果DataFrame  
         # 初始化结果DataFrame  
         res = pd.DataFrame()  
         res = pd.DataFrame()  
@@ -69,14 +69,14 @@ class PowerOscillationAnalyst(Analyst):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):  
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):  
             for file_name in file_names:  
             for file_name in file_names:  
 
 
-                if not file_name.endswith(CSVSuffix):
+                if not file_name.endswith(".csv"):
                     continue
                     continue
 
 
                 file_path = os.path.join(root, file_name)  
                 file_path = os.path.join(root, file_name)  
                 frame = pd.read_csv(file_path, encoding=encoding)  
                 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
                 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
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
 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):
 class RatedPowerWindSpeedAnalyst(Analyst):
@@ -38,32 +38,23 @@ class RatedPowerWindSpeedAnalyst(Analyst):
         outputAnalysisDir (str): 分析输出目录。  
         outputAnalysisDir (str): 分析输出目录。  
         confData (ConfBusiness): 配置
         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) & (
         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) & (
         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℃的功率分布图
         # 绘制环境温度大于等于25℃的功率分布图
+        # fig, ax = plt.subplots(figsize=(16, 8))
         fig, ax = plt.subplots()
         fig, ax = plt.subplots()
         sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=over_temp, fliersize=0, ax=ax,
         sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=over_temp, fliersize=0, ax=ax,
                     medianprops={'linestyle': '-', 'color': 'red'},
                     medianprops={'linestyle': '-', 'color': 'red'},
                     boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
                     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_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.xticks(rotation=45)  # 旋转45度
         plt.savefig(os.path.join(outputAnalysisDir,
         plt.savefig(os.path.join(outputAnalysisDir,
                     "额定满发风速功率分布(10min)(环境温度大于25度).png"), bbox_inches='tight', dpi=120)
                     "额定满发风速功率分布(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,
         sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=below_temp, fliersize=0, ax=ax,
                     medianprops={'linestyle': '-', 'color': 'red'},
                     medianprops={'linestyle': '-', 'color': 'red'},
                     boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
                     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_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.xticks(rotation=45)  # 旋转45度
         plt.savefig(os.path.join(outputAnalysisDir,
         plt.savefig(os.path.join(outputAnalysisDir,
                     "额定满发风速功率分布(10min)(环境温度小于25度).png"), bbox_inches='tight', dpi=120)
                     "额定满发风速功率分布(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
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
 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):
 class RatedWindSpeedAnalyst(Analyst):
@@ -57,7 +57,7 @@ class RatedWindSpeedAnalyst(Analyst):
         sns.barplot(x=Field_NameOfTurbine, y='count',
         sns.barplot(x=Field_NameOfTurbine, y='count',
                     data=data, ax=ax, color='dodgerblue')
                     data=data, ax=ax, color='dodgerblue')
         ax.set_title('Rated - full wind speed interval data count')
         ax.set_title('Rated - full wind speed interval data count')
-        ax.grid(True)
+        
         # 旋转45度
         # 旋转45度
         plt.xticks(rotation=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
 import plotly.graph_objects as go
 from plotly.subplots import make_subplots
 from plotly.subplots import make_subplots
 from geopy.distance import geodesic
 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):
 class TemperatureEnvironmentAnalyst(Analyst):
@@ -21,20 +21,14 @@ class TemperatureEnvironmentAnalyst(Analyst):
     def typeAnalyst(self):
     def typeAnalyst(self):
         return "temperature_environment"
         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)
         turbineInfos = turbineInfo.loadTurbineInfo(confData.turbineInfoFilePathCSV)
 
 
         #  环境温度 分析
         #  环境温度 分析
         turbineEnvTempData =dataFrameMerge.groupby(Field_NameOfTurbine).agg(
         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)
         mergeData = self.merge_Data(Field_NameOfTurbine,turbineInfos, turbineEnvTempData,confData)
-        
+        print(mergeData.head())
         self.draw(mergeData,outputAnalysisDir, confData)
         self.draw(mergeData,outputAnalysisDir, confData)
     
     
 
 
@@ -58,7 +52,8 @@ class TemperatureEnvironmentAnalyst(Analyst):
         'left': 左连接,保留左边DataFrame的所有键,以及右边DataFrame中匹配的键的行。
         'left': 左连接,保留左边DataFrame的所有键,以及右边DataFrame中匹配的键的行。
         'right': 右连接,保留右边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')
         merge_data = pd.merge(turbineInfos, turbineEnvTempData, on=[fieldTurbineName], how='inner')
 
 
         return merge_data
         return merge_data
@@ -89,7 +84,7 @@ class TemperatureEnvironmentAnalyst(Analyst):
             target_tuple = (center[0], current_temp)
             target_tuple = (center[0], current_temp)
             if target_tuple in nearby_points:
             if target_tuple in nearby_points:
                 nearby_points.remove(target_tuple)
                 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.append((center[0], nearby_points, mean_temp, current_temp))
         res = pd.DataFrame(res, columns=[Field_NameOfTurbine, '周边机组', '周边机组温度', '当前机组温度'])
         res = pd.DataFrame(res, columns=[Field_NameOfTurbine, '周边机组', '周边机组温度', '当前机组温度'])
         res[self.fieldTemperatureDiff] = res['当前机组温度'] - res['周边机组温度']
         res[self.fieldTemperatureDiff] = res['当前机组温度'] - res['周边机组温度']
@@ -112,5 +107,5 @@ class TemperatureEnvironmentAnalyst(Analyst):
         
         
         sns.barplot(x=Field_NameOfTurbine ,y='当前机组温度',data=res,ax=ax2,color='dodgerblue')
         sns.barplot(x=Field_NameOfTurbine ,y='当前机组温度',data=res,ax=ax2,color='dodgerblue')
         ax2.set_ylabel('temperature')
         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)
         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 pandas as pd
 import matplotlib.pyplot as plt
 import matplotlib.pyplot as plt
 import seaborn as sns
 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
         # Ensure the necessary columns are of float type
         dataFrame['wind_speed'] = dataFrame[field_wind_speed].astype(float)
         dataFrame['wind_speed'] = dataFrame[field_wind_speed].astype(float)
         dataFrame['rotor_speed'] = dataFrame[field_rotort_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
         # Group by 'power_floor' and calculate mean, max, and min of TSR
         grouped = dataFrame.groupby('power_floor').agg({
         grouped = dataFrame.groupby('power_floor').agg({
@@ -59,11 +58,11 @@ class TSRAnalyst(AnalystExcludeRatedPower):
         grouped.to_csv(output_path, index=False)
         grouped.to_csv(output_path, index=False)
 
 
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
     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:
         Parameters:
         - csvFileDirOfCp: str, path to the directory containing input CSV files.
         - csvFileDirOfCp: str, path to the directory containing input CSV files.
@@ -73,9 +72,7 @@ class TSRAnalyst(AnalystExcludeRatedPower):
         field_Name_Turbine = "turbine_name"
         field_Name_Turbine = "turbine_name"
         x_name = 'power_floor'
         x_name = 'power_floor'
         y_name = 'tsr'
         y_name = 'tsr'
-
-        upLimitOfPower = confData.rated_power*0.9
-        upLimitOfTSR = 20
+        split_way = '_tsr.csv'
 
 
         # 设置绘图样式
         # 设置绘图样式
         sns.set_palette('deep')
         sns.set_palette('deep')
@@ -87,16 +84,16 @@ class TSRAnalyst(AnalystExcludeRatedPower):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
             for file_name in file_names:
             for file_name in file_names:
 
 
-                if not file_name.endswith(CSVSuffix):
+                if not file_name.endswith(".csv"):
                     continue
                     continue
 
 
                 file_path = os.path.join(root, file_name)
                 file_path = os.path.join(root, file_name)
 
 
                 # 读取CSV文件
                 # 读取CSV文件
                 frame = pd.read_csv(file_path, encoding=encoding)
                 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
                 frame[field_Name_Turbine] = turbine_name
@@ -112,21 +109,10 @@ class TSRAnalyst(AnalystExcludeRatedPower):
         fig, ax = plt.subplots(figsize=(16, 8))
         fig, ax = plt.subplots(figsize=(16, 8))
         ax = sns.lineplot(x=x_name, y=y_name, data=ress,
         ax = sns.lineplot(x=x_name, y=y_name, data=ress,
                           hue=field_Name_Turbine)
                           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')
         ax.set_title('TSR-Distibute')
         # plt.legend(ncol=4)
         # 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)
                     bbox_inches='tight', dpi=300)
         plt.close(fig)
         plt.close(fig)
 
 
@@ -139,18 +125,7 @@ class TSRAnalyst(AnalystExcludeRatedPower):
                               palette=sns.set_palette(color), legend=False)
                               palette=sns.set_palette(color), legend=False)
             ax = sns.lineplot(x=x_name, y=y_name, data=group,
             ax = sns.lineplot(x=x_name, y=y_name, data=group,
                               color='darkblue', legend=False)
                               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))
             ax.set_title('turbine name={}'.format(name))
-            plt.xticks(rotation=45)  # 旋转45度
             plt.savefig(csvFileDirOfCp + r"/{}.png".format(name),
             plt.savefig(csvFileDirOfCp + r"/{}.png".format(name),
                         bbox_inches='tight', dpi=120)
                         bbox_inches='tight', dpi=120)
             plt.close(fig)
             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 numpy as np
 import pandas as pd
 import pandas as pd
 import matplotlib.pyplot as plt
 import matplotlib.pyplot as plt
-from matplotlib.ticker import MultipleLocator
 import seaborn as sns
 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
         # Ensure the necessary columns are of float type
         dataFrame['wind_speed'] = dataFrame[field_wind_speed].astype(float)
         dataFrame['wind_speed'] = dataFrame[field_wind_speed].astype(float)
         dataFrame['rotor_speed'] = dataFrame[field_rotort_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
         # Group by 'wind_speed_floor' and calculate mean, max, and min of TSR
         grouped = dataFrame.groupby('wind_speed_floor').agg({
         grouped = dataFrame.groupby('wind_speed_floor').agg({
@@ -59,11 +58,11 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
         grouped.to_csv(output_path, index=False)
         grouped.to_csv(output_path, index=False)
 
 
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
     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:
         Parameters:
         - csvFileDirOfCp: str, path to the directory containing input CSV files.
         - csvFileDirOfCp: str, path to the directory containing input CSV files.
@@ -73,8 +72,7 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
         field_Name_Turbine = "turbine_name"
         field_Name_Turbine = "turbine_name"
         x_name = 'wind_speed_floor'
         x_name = 'wind_speed_floor'
         y_name = 'tsr'
         y_name = 'tsr'
-
-        upLimitOfTSR=20
+        split_way = '_tsr_windspeed.csv'
 
 
         # 设置绘图样式
         # 设置绘图样式
         sns.set_palette('deep')
         sns.set_palette('deep')
@@ -86,7 +84,7 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
         for root, dir_names, file_names in dir.list_directory(csvFileDirOfCp):
             for file_name in file_names:
             for file_name in file_names:
 
 
-                if not file_name.endswith(CSVSuffix):
+                if not file_name.endswith(".csv"):
                     continue
                     continue
 
 
                 file_path = os.path.join(root, file_name)
                 file_path = os.path.join(root, file_name)
@@ -95,7 +93,7 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
                 frame = pd.read_csv(file_path, encoding=encoding)
                 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
                 frame[field_Name_Turbine] = turbine_name
@@ -111,19 +109,9 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
         fig, ax = plt.subplots(figsize=(16, 8))
         fig, ax = plt.subplots(figsize=(16, 8))
         ax = sns.lineplot(x=x_name, y=y_name, data=ress,
         ax = sns.lineplot(x=x_name, y=y_name, data=ress,
                           hue=field_Name_Turbine)
                           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')
         ax.set_title('TSR-Distibute')
         plt.legend(ncol=4)
         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)
                     bbox_inches='tight', dpi=300)
         plt.close(fig)
         plt.close(fig)
 
 
@@ -136,17 +124,7 @@ class TSRWindSpeedAnalyst(AnalystExcludeRatedPower):
                               palette=sns.set_palette(color), legend=False)
                               palette=sns.set_palette(color), legend=False)
             ax = sns.lineplot(x=x_name, y=y_name, data=group,
             ax = sns.lineplot(x=x_name, y=y_name, data=group,
                               color='darkblue', legend=False)
                               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))
             ax.set_title('turbine name={}'.format(name))
-            plt.xticks(rotation=45)  # 旋转45度
             plt.savefig(csvFileDirOfCp + r"/{}.png".format(name),
             plt.savefig(csvFileDirOfCp + r"/{}.png".format(name),
                         bbox_inches='tight', dpi=120)
                         bbox_inches='tight', dpi=120)
             plt.close(fig)
             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
 import matplotlib.pyplot as plt
 from matplotlib.ticker import MultipleLocator
 from matplotlib.ticker import MultipleLocator
 from windrose import WindroseAxes
 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.cm import get_cmap 
 from matplotlib.colors import ListedColormap   
 from matplotlib.colors import ListedColormap   
 
 
@@ -25,12 +25,7 @@ class WinRoseOfTurbineAnalyst(Analyst):
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
     def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
         self.windRoseAnalysis(dataFrameMerge, outputAnalysisDir, confData)
         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]  
         bins = [0, 3, 6, 9, np.inf]  
         speed_labels = ['[0,3)', '[3,6)', '[6,9)', '>=9']  
         speed_labels = ['[0,3)', '[3,6)', '[6,9)', '>=9']  
@@ -43,7 +38,7 @@ class WinRoseOfTurbineAnalyst(Analyst):
         # 按设备名分组数据
         # 按设备名分组数据
         grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
         grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
         print("self.ratedPower {}".format(confData.rated_power))
         print("self.ratedPower {}".format(confData.rated_power))
-        # 遍历每个设备并绘制图
+        # 遍历每个设备并绘制散点
         for name, group in grouped:            
         for name, group in grouped:            
             # 对风速进行分箱处理,但不添加到DataFrame中  
             # 对风速进行分箱处理,但不添加到DataFrame中  
             speed_bins = pd.cut(group[confData.field_wind_speed], bins=bins, labels=speed_labels)  
             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)  
                 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_zero_location('N')  # 设置0度位置为北  
             ax.set_theta_direction(-1)  # 设置角度方向为顺时针  
             ax.set_theta_direction(-1)  # 设置角度方向为顺时针  
             ax.set_yticklabels([])  # 不显示y轴刻度标签  
             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
 import pandas as pd
-from utils.jsonUtil import JsonUtil
+from algorithm.utils.jsonUtil.jsonUtil import JsonUtil
 
 
 # 全局变量
 # 全局变量
 charset_unify = 'utf-8'
 charset_unify = 'utf-8'
-CSVSuffix = '.csv'
 
 
 Field_NameOfTurbine = "turbine_name"
 Field_NameOfTurbine = "turbine_name"
 Field_GeneratorTorque = "generator_torque"
 Field_GeneratorTorque = "generator_torque"
 Field_GeneratorSpeed = "generator_speed"
 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:
 class ConfBusiness:
@@ -63,7 +43,7 @@ class ConfBusiness:
         # 将字符串转换为 pd.Timestamp 类型
         # 将字符串转换为 pd.Timestamp 类型
         self.start_time = None
         self.start_time = None
         self.end_time = None
         self.end_time = None
-        self.excludingMonths = None  # 排除指定的月份数据 格式%Y-%m
+        self.excludingMonths=None # 排除指定的月份数据 格式%Y-%m
 
 
         self.field_turbine_time = None    # 字段名 时间
         self.field_turbine_time = None    # 字段名 时间
         self.field_turbine_name = None    # 字段名 机组名
         self.field_turbine_name = None    # 字段名 机组名
@@ -75,8 +55,8 @@ class ConfBusiness:
         self.field_pitch_angle3 = None    # 字段名 桨距角3
         self.field_pitch_angle3 = None    # 字段名 桨距角3
         self.field_turbine_state = None   # 字段名 风机状态
         self.field_turbine_state = None   # 字段名 风机状态
         self.field_gen_speed = 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_min = None       # 值 发电机转速最小
         self.value_gen_speed_max = None       # 值 发电机转速最大
         self.value_gen_speed_max = None       # 值 发电机转速最大
         self.field_rotor_speed = None     # 字段名 叶轮转速
         self.field_rotor_speed = None     # 字段名 叶轮转速
@@ -87,14 +67,8 @@ class ConfBusiness:
         self.field_env_temp = None        # 字段名 环境温度
         self.field_env_temp = None        # 字段名 环境温度
         self.field_nacelle_temp = None    # 字段名 机舱温度
         self.field_nacelle_temp = None    # 字段名 机舱温度
         self.field_temperature_large_components = 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)
         configData = JsonUtil.read_json(jsonFilePath)
-
+                
         self.farm_name = configData['name_PowerFarm']
         self.farm_name = configData['name_PowerFarm']
         self.rated_power = configData['rated_Power_Turbine_Unit_kW']
         self.rated_power = configData['rated_Power_Turbine_Unit_kW']
         self.rated_WindSpeed = configData["rated_WindSpeed"]
         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.rotor_diameter = configData['rotor_diameter']
         self.rotational_Speed_Ratio = configData['rotational_Speed_Ratio']
         self.rotational_Speed_Ratio = configData['rotational_Speed_Ratio']
         self.density_air = configData['density_air']
         self.density_air = configData['density_air']
@@ -143,7 +114,7 @@ class ConfBusiness:
             self.start_time_str, format='%Y-%m-%d %H:%M:%S')
             self.start_time_str, format='%Y-%m-%d %H:%M:%S')
         self.end_time = pd.to_datetime(
         self.end_time = pd.to_datetime(
             self.end_time_str, format='%Y-%m-%d %H:%M:%S')
             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_time = configData['turbine_Time']
         self.field_turbine_name = configData['turbine_Name']
         self.field_turbine_name = configData['turbine_Name']
@@ -155,10 +126,10 @@ class ConfBusiness:
         self.field_pitch_angle3 = configData['pitch_Angle3']
         self.field_pitch_angle3 = configData['pitch_Angle3']
         self.field_turbine_state = configData['state_Turbine']
         self.field_turbine_state = configData['state_Turbine']
         self.field_gen_speed = configData['speed_Generator']
         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_rotor_speed = configData['speed_Rotor']
         self.field_torque = configData['torque']
         self.field_torque = configData['torque']
         self.field_wind_dir = configData['direction_Wind']
         self.field_wind_dir = configData['direction_Wind']
@@ -167,22 +138,16 @@ class ConfBusiness:
         self.field_env_temp = configData['temperature_Env']
         self.field_env_temp = configData['temperature_Env']
         self.field_nacelle_temp = configData['temperature_Nacelle']
         self.field_nacelle_temp = configData['temperature_Nacelle']
         self.field_temperature_large_components = configData['temperature_large_components']
         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
         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 pandas as pd
-import importlib
+from algorithm.utils.jsonUtil.jsonUtil import JsonUtil
 import concurrent.futures
 import concurrent.futures
-from utils.jsonUtil import JsonUtil
-from algorithmContract.confBusiness import ConfBusiness
+import confBusiness
+import importlib
 from algorithm.dataProcessor import DataProcessor
 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
 import seaborn as sns
 sns.set_style("darkgrid") 
 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)
     module = importlib.import_module(module_path)
     # 获取类
     # 获取类
@@ -19,6 +19,7 @@ def buildDynamicInstance(module_path, class_name, confData: ConfBusiness):
 
 
     return instance
     return instance
 
 
+
 def dynamic_instance_and_call(module_path, class_name, method_name, *args, **kwargs):
 def dynamic_instance_and_call(module_path, class_name, method_name, *args, **kwargs):
     # 动态导入模块
     # 动态导入模块
     module = importlib.import_module(module_path)
     module = importlib.import_module(module_path)
@@ -42,7 +43,7 @@ def loadJson(filePath):
 def executeAnalysis(config):
 def executeAnalysis(config):
     configBusinessFilePath=config["configFilePath"]
     configBusinessFilePath=config["configFilePath"]
     print(configBusinessFilePath)
     print(configBusinessFilePath)
-    businessConfig=ConfBusiness()
+    businessConfig=confBusiness.ConfBusiness()
     configBusiness = businessConfig.loadConfig(configBusinessFilePath)
     configBusiness = businessConfig.loadConfig(configBusinessFilePath)
 
 
     baseAnalysts=[]
     baseAnalysts=[]
@@ -54,15 +55,26 @@ def executeAnalysis(config):
         className = dynamicAnalyst["className"]
         className = dynamicAnalyst["className"]
         methodName = dynamicAnalyst["methodName"]
         methodName = dynamicAnalyst["methodName"]
         analyst = buildDynamicInstance(package, className, configBusiness)
         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()
     process = DataProcessor()
 
 
+    for analyst in baseAnalysts:
+        process.attachBaseAnalyst(analyst)
+
     for analyst in analysts:
     for analyst in analysts:
         process.attach(analyst)
         process.attach(analyst)
+
     process.execute(configBusiness)
     process.execute(configBusiness)
 
 
+    for analyst in baseAnalysts:
+        process.detachBaseAnalyst(analyst)
+
     for analyst in analysts:
     for analyst in analysts:
         process.detach(analyst)
         process.detach(analyst)
 
 
@@ -74,7 +86,7 @@ if __name__ == "__main__":
         executeAnalysis(config)
         executeAnalysis(config)
 
 
     # 使用多线程时,matplotlib绘图报错
     # 使用多线程时,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对象。
     # 这个错误通常是因为matplotlib的图形后端(backend)依赖于Qt框架,而Qt框架要求在一个QApplication实例被创建之后再创建任何QWidget对象。
     # 在多线程环境中,每个线程都应该有一个它自己的事件循环。然而,QApplication实例通常是全局的,并且应该只在主线程中创建一次。
     # 在多线程环境中,每个线程都应该有一个它自己的事件循环。然而,QApplication实例通常是全局的,并且应该只在主线程中创建一次。
     # 如果你尝试在一个非主线程中创建QApplication或者QWidget,就会遇到这个问题。
     # 如果你尝试在一个非主线程中创建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
 import pandas as pd
 
 
 
 

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

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