import numpy as np
import pandas as pd
import os
import plotly.graph_objects as go

# 声明全局变量
fieldActivePower = "功率"
fieldWindSpeed = "风速"
fieldPitchAngle = "叶片角度"
PRated = 1500  # 风机额定功率
VCutOut = 25  # 切出风速
VCutIn = 3  # 切入风速
VRated = 10  # 额定风速
VNum = int(VCutOut / 0.25)  # 风速分区数量

# 读取数据函数


def read_data():
    # 读取 CSV 文件
    March809 = pd.read_csv(
        "./data/DataClassificationIdentification/A01-G.csv", encoding="utf-8")
    IdealCurve = pd.read_csv(
        "./data/DataClassificationIdentification/A型风机设计功率曲线.csv", encoding="utf-8")
    return March809, IdealCurve

# 计算统计信息函数


def calculate_statistics(March809, IdealCurve):
    AA = len(March809)
    BB = len(IdealCurve)
    PowerMax = March809[fieldActivePower].max()
    PowerRated = int(np.ceil(PowerMax / 100) * 100)
    PNum = PowerRated // 25  # 功率分区数量

    # 计算实际发电量
    EPActualTotal = March809[March809[fieldActivePower]
                             >= 0][fieldActivePower].sum() / 6
    WindSpeedAvr = March809[fieldWindSpeed].mean()

    # 计算风机可利用率
    nShouldGP = np.sum(March809[fieldWindSpeed] >= VCutIn)
    nRealGP = np.sum((March809[fieldWindSpeed] >= VCutIn)
                     & (March809[fieldActivePower] > 0))
    TurbineRunRate = (nRealGP / nShouldGP * 100) if nShouldGP > 0 else 0

    # 计算理论发电量
    EPIdealTotalAAA = 0
    for i in range(AA):
        nWhichBin = 0
        for m in range(BB - 1):
            if IdealCurve.iloc[m][fieldWindSpeed] < March809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
                nWhichBin = m
                break
        if nWhichBin > 0:
            IdealPower = (March809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBin][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBin + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBin]
                                                                                                            [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBin + 1][fieldActivePower] - IdealCurve.iloc[nWhichBin][fieldActivePower]) + IdealCurve.iloc[nWhichBin][fieldActivePower]
            EPIdealTotalAAA += IdealPower / 6

    return AA, BB, PNum, EPActualTotal, WindSpeedAvr, TurbineRunRate, EPIdealTotalAAA

# 分类数据函数


def classify_data(March809, PNum, VNum):
    DzMarch809 = March809[March809[fieldActivePower] > 0]
    nCounter1 = len(DzMarch809)

    XBoxNumber = np.ones((PNum, VNum), dtype=int)
    for i in range(nCounter1):
        nWhichP = np.digitize(
            DzMarch809.iloc[i][fieldActivePower], np.arange(0, PNum * 25, 25)) - 1
        nWhichV = np.digitize(
            DzMarch809.iloc[i][fieldWindSpeed], np.arange(0.125, VNum * 0.25, 0.25))
        if nWhichP < PNum and nWhichV < VNum:
            XBoxNumber[nWhichP, nWhichV] += 1
    XBoxNumber -= 1

    return DzMarch809, XBoxNumber

# 计算百分比函数


def compute_percentages(XBoxNumber, PNum, VNum):
    PBoxPercent = np.zeros((PNum, VNum))
    PBinSum = XBoxNumber.sum(axis=1)
    for i in range(PNum):
        if PBinSum[i] > 0:
            PBoxPercent[i, :] = XBoxNumber[i, :] / PBinSum[i] * 100

    VBoxPercent = np.zeros((PNum, VNum))
    VBinSum = XBoxNumber.sum(axis=0)
    for i in range(VNum):
        if VBinSum[i] > 0:
            VBoxPercent[:, i] = XBoxNumber[:, i] / VBinSum[i] * 100

    return PBoxPercent, VBoxPercent

# 查找主带函数


def find_main_band(PBoxPercent, PNum, VNum, XBoxNumber):
    PBoxMaxIndex = np.argmax(PBoxPercent, axis=1)
    PBoxMaxP = np.max(PBoxPercent, axis=1)

    DotDense = np.zeros(PNum)
    DotDenseLeftRight = np.zeros((PNum, 2), dtype=int)
    DotValve = 90
    for i in range(PNum - 6):
        PDotDenseSum = PBoxMaxP[i]
        iSpreadRight = iSpreadLeft = 1
        while PDotDenseSum < DotValve:
            if (PBoxMaxIndex[i] + iSpreadRight) < VNum - 1:
                PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[i] + iSpreadRight]
                iSpreadRight += 1
            if (PBoxMaxIndex[i] + iSpreadRight) > VNum - 1:
                break
            if (PBoxMaxIndex[i] - iSpreadLeft) > 0:
                PDotDenseSum += PBoxPercent[i, PBoxMaxIndex[i] - iSpreadLeft]
                iSpreadLeft += 1
            if (PBoxMaxIndex[i] - iSpreadLeft) <= 0:
                break
        iSpreadRight -= 1
        iSpreadLeft -= 1
        DotDenseLeftRight[i, :] = [iSpreadLeft, iSpreadRight]
        DotDense[i] = iSpreadLeft + iSpreadRight + 1

    DotDenseWidthLeft = DotDenseLeftRight[:, 1]
    MainBandRight = np.median(DotDenseWidthLeft)
    PowerLimitValve = int(np.ceil(MainBandRight)) + 3

    PowerLimit = np.zeros(PNum)
    nCounterLimit = nCounter = 0
    WidthAverage = 0
    for i in range(PNum - 6):
        if DotDenseLeftRight[i, 1] > PowerLimitValve and XBoxNumber[i, :].sum() > 20:
            PowerLimit[i] = 1
            nCounterLimit += 1
        else:
            WidthAverage += DotDenseLeftRight[i, 1]
            nCounter += 1
    WidthAverage /= nCounter

    WidthVar = np.sqrt(np.mean((DotDenseLeftRight[:, 1] - WidthAverage) ** 2))
    PowerBandWidth = WidthAverage * 2 * 0.25

    return DotDense, DotDenseLeftRight, PowerLimit, WidthAverage, WidthVar, PowerBandWidth, PBoxMaxIndex

# 标记坏点函数


def mark_bad_points(DzMarch809, DotDenseLeftRight, PBoxMaxIndex, PowerLimit, PNum, VNum, XBoxNumber, PBoxPercent):
    CurveWidthR = int(np.ceil(DotDenseLeftRight[:, 1].mean())) + 2
    CurveWidthL = CurveWidthR
    BBoxLimit = np.zeros((PNum, VNum))
    for i in range(3, PNum - 6):
        if PowerLimit[i] == 1:
            BBoxLimit[i, PBoxMaxIndex[i] + CurveWidthR + 1: VNum] = 1

    BBoxRemove = np.zeros((PNum, VNum))
    for m in range(PNum - 6):
        BBoxRemove[m, PBoxMaxIndex[m] + CurveWidthR: VNum] = 1
        BBoxRemove[m, :PBoxMaxIndex[m] - CurveWidthL + 1] = 2

    CurveTop = [0, 0]
    CurveTopValve = 3
    BTopFind = False
    for m in range(PNum - 4, 0, -1):
        for n in range(VNum):
            if PBoxPercent[m, n] > CurveTopValve and XBoxNumber[m, n] >= 10:
                CurveTop = [m, n]
                BTopFind = True
                break
        if BTopFind:
            break

    IsolateValve = 3
    for m in range(PNum - 6):
        for n in range(PBoxMaxIndex[m] + CurveWidthR, VNum):
            if PBoxPercent[m, n] < IsolateValve:
                BBoxRemove[m, n] = 1

    for m in range(PNum - 6, PNum):
        BBoxRemove[m, :] = 3
    for m in range(PNum - 5, PNum):
        BBoxRemove[m, :CurveTop[1] - 2] = 2

    DzMarch809Sel = np.zeros(len(DzMarch809), dtype=int)
    for i in range(len(DzMarch809)):
        nWhichP = np.digitize(
            DzMarch809.iloc[i][fieldActivePower], np.arange(0, PNum * 25, 25)) - 1
        nWhichV = np.digitize(
            DzMarch809.iloc[i][fieldWindSpeed], np.arange(0.125, VNum * 0.25, 0.25))
        
        # if (
        #     (DzMarch809.iloc[i][fieldActivePower] < PRated * 0.75) and
        #     (DzMarch809.iloc[i][fieldPitchAngle] > 0.5) or
        #     (DzMarch809.iloc[i][fieldActivePower] < PRated * 0.85) and
        #     (DzMarch809.iloc[i][fieldPitchAngle] > 1.5) or
        #     (DzMarch809.iloc[i][fieldActivePower] < PRated * 0.9) and
        #     (DzMarch809.iloc[i][fieldPitchAngle] > 2.5)
        # ):
        #     continue

        if nWhichP < PNum and nWhichV < VNum:
            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 identify_limit_load_data(DzMarch809: pd.DataFrame, DzMarch809Sel, PRated):
    nCounter1 = len(DzMarch809)
    PVLimit = np.zeros((nCounter1, 2))
    nLimitTotal = 0
    nWindowLength = 3
    PowerStd = 15  # 功率波动方差
    PowerLimitUp =  PRated - 300
    PowerLimitLow = 5  # 200kW 

    nWindowNum = nCounter1 // nWindowLength

    for i in range(nWindowNum):
        LimitWindow = DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldActivePower].values
               
        # 检查所有数据是否在 PowerLimitLow值~PowerLimitUp值范围内
        if not ((LimitWindow >= PowerLimitLow) & (LimitWindow <= PowerLimitUp)).all():
            continue
        
        """
        限功率识别策略（参考）：
        1. 功率<额定功率(PRated)*0.75 and  叶片角度>0.5 ; 2. 功率<额定功率*0.85 and 叶片角度>1.5 ; 3. 功率<额定功率*0.9 and 叶片角度>2.5；
        示例：
        1. 功率<1100，and  叶片角度>0.5 ; 2. 功率<1250 and 叶片角度>1.5 ; 3. 功率<1400 and 叶片角度>2.5 ;
        """        
        # 额外的限功率识别策略
        pitch_angle_window = DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldPitchAngle].values

        if (
            (DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldActivePower] < PRated * 0.75).any() and
            (pitch_angle_window > 0.5).any() or
            (DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldActivePower] < PRated * 0.85).any() and
            (pitch_angle_window > 1.5).any() or
            (DzMarch809.iloc[i * nWindowLength:(i + 1) * nWindowLength][fieldActivePower] < PRated * 0.9).any() and
            (pitch_angle_window > 2.5).any()
        ):
            # 标识限负荷数据
            DzMarch809Sel[i * nWindowLength:(i + 1) * nWindowLength] = 4

        UpLimit = LimitWindow[0] + PowerStd
        LowLimit = LimitWindow[0] - PowerStd

        # 检查所有数据是否在方差范围内
        if not ((LimitWindow[1:] >= LowLimit) & (LimitWindow[1:] <= UpLimit)).all():
            continue

        # 标识限负荷数据
        DzMarch809Sel[i * nWindowLength:(i + 1) * nWindowLength] = 4

        for j in range(nWindowLength):
            # 只提取功率和风速数据
            PVLimit[nLimitTotal] = DzMarch809.iloc[i * nWindowLength + j][[fieldWindSpeed, fieldActivePower]]
            nLimitTotal += 1

    PVLimit = PVLimit[:nLimitTotal]  # 截取实际数据部分

    return PVLimit, DzMarch809Sel

# 计算能量损失函数
def calculate_energy_loss(DzMarch809Sel, DzMarch809, IdealCurve, PNum, BB, PRated, EPIdealTotalAAA, EPActualTotal):
    EPLostStopTotal = 0
    nStopTotal = 0
    for i in range(len(DzMarch809)):
        if DzMarch809.iloc[i][fieldActivePower] <= 0:
            nWhichBin = 0
            for m in range(BB - 1):
                if IdealCurve.iloc[m][fieldWindSpeed] < DzMarch809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
                    nWhichBin = m
                    break
            if nWhichBin > 0:
                IdealPower = (DzMarch809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBin][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBin + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBin]
                                                                                                                  [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBin + 1][fieldActivePower] - IdealCurve.iloc[nWhichBin][fieldActivePower]) + IdealCurve.iloc[nWhichBin][fieldActivePower]
                EPLostStopTotal += IdealPower / 6
                nStopTotal += 1

    EPLostBadTotal = 0
    EPLost = 0
    nBadTotal = 0
    for i in range(len(DzMarch809)):
        if DzMarch809Sel[i] == 1:
            nWhichBin = 0
            for m in range(BB - 1):
                if IdealCurve.iloc[m][fieldWindSpeed] < DzMarch809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
                    nWhichBin = m
                    break
            if nWhichBin > 0:
                IdealPower = (DzMarch809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBin][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBin + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBin]
                                                                                                                  [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBin + 1][fieldActivePower] - IdealCurve.iloc[nWhichBin][fieldActivePower]) + IdealCurve.iloc[nWhichBin][fieldActivePower]
                EPLost += abs(IdealPower -
                              DzMarch809.iloc[i][fieldActivePower]) / 6
                EPLostBadTotal += EPLost
                nBadTotal += 1

    EPOverTotal = 0
    nOverTotal = 0
    for i in range(len(DzMarch809)):
        if DzMarch809Sel[i] == 3:
            EPOver = (DzMarch809.iloc[i][fieldActivePower] - PRated) / 6
            EPOverTotal += EPOver
            nOverTotal += 1

    EPLostPerformTotal = 0
    for i in range(len(DzMarch809)):
        nWhichBinI = 0
        for m in range(BB - 1):
            if IdealCurve.iloc[m][fieldWindSpeed] < DzMarch809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
                nWhichBinI = m
                break
        if nWhichBinI > 0:
            IdealPower = (DzMarch809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBinI][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBinI + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBinI]
                                                                                                               [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBinI + 1][fieldActivePower] - IdealCurve.iloc[nWhichBinI][fieldActivePower]) + IdealCurve.iloc[nWhichBinI][fieldActivePower]
            EPLostPerformTotal += (IdealPower -
                                   DzMarch809.iloc[i][fieldActivePower]) / 6

    EPIdealTotal = EPActualTotal + EPLostStopTotal + EPLostBadTotal + \
        EPLostPerformTotal if EPLostPerformTotal >= 0 else EPActualTotal + \
        EPLostStopTotal + EPLostBadTotal

    RemoveOverEP = 0
    for i in range(len(DzMarch809)):
        if DzMarch809Sel[i] == 2:
            nWhichBin = 0
            for m in range(BB - 1):
                if IdealCurve.iloc[m][fieldWindSpeed] < DzMarch809.iloc[i][fieldWindSpeed] <= IdealCurve.iloc[m + 1][fieldWindSpeed]:
                    nWhichBin = m
                    break
            if nWhichBin > 0:
                IdealPower = (DzMarch809.iloc[i][fieldWindSpeed] - IdealCurve.iloc[nWhichBin][fieldWindSpeed]) / (IdealCurve.iloc[nWhichBin + 1][fieldWindSpeed] - IdealCurve.iloc[nWhichBin]
                                                                                                                  [fieldWindSpeed]) * (IdealCurve.iloc[nWhichBin + 1][fieldActivePower] - IdealCurve.iloc[nWhichBin][fieldActivePower]) + IdealCurve.iloc[nWhichBin][fieldActivePower]
                RemoveOverEP += (DzMarch809.iloc[i]
                                 [fieldActivePower] - IdealPower) / 6

    for i in range(len(DzMarch809)):
        if DzMarch809.iloc[i][fieldActivePower] > PRated:
            RemoveOverEP += (DzMarch809.iloc[i][fieldActivePower] - PRated) / 6

    return EPLostStopTotal, EPLostBadTotal, EPOverTotal, EPLostPerformTotal, EPIdealTotal - RemoveOverEP

# 计算实测功率曲线函数


def calculate_measured_power_curve(PVDot, VRated, PRated):
    XBinNumber = np.ones(50)
    PCurve = np.zeros((50, 2))
    PCurve[:, 0] = np.arange(0.5, 25.5, 0.5)
    XBinSum = np.zeros((50, 2))

    for i in range(len(PVDot)):
        nWhichBin = 0
        for b in range(50):
            if (b * 0.5 - 0.25) < PVDot.iloc[i][fieldWindSpeed] <= (b * 0.5 + 0.25):
                nWhichBin = b
                break

        if nWhichBin > 0:
            XBinSum[nWhichBin, 0] += PVDot.iloc[i][fieldWindSpeed]
            XBinSum[nWhichBin, 1] += PVDot.iloc[i][fieldActivePower]
            XBinNumber[nWhichBin] += 1

    XBinNumber -= 1

    for b in range(50):
        if XBinNumber[b] > 0:
            PCurve[b, 0] = XBinSum[b, 0] / XBinNumber[b]
            PCurve[b, 1] = XBinSum[b, 1] / XBinNumber[b]

    VRatedNum = int(VRated / 0.5)
    for m in range(VRatedNum, 50):
        if PCurve[m, 1] == 0:
            PCurve[m, 1] = PRated

    return PCurve

# 计算标准正则功率曲线函数
def calculate_normalized_power_curve(IdealCurve, VRated, PRated):
    PCurveNorm = np.zeros((50, 2))
    VRatedNum = int(VRated / 0.5)

    # 15m/s以上为额定功率
    high_wind_speeds = np.arange(15, 25.5, 0.5)
    PCurveNorm[VRatedNum:VRatedNum+len(high_wind_speeds), 0] = high_wind_speeds
    PCurveNorm[VRatedNum:VRatedNum+len(high_wind_speeds), 1] = PRated

    # 15m/s以下正则功率曲线
    VSpeed = np.arange(0.5, 15.5, 0.5)
    CurveData = IdealCurve[IdealCurve[fieldWindSpeed]
                           <= 15].to_numpy()  # 提取风速<=15的数据

    for i, v in enumerate(VSpeed):
        if i < len(CurveData) - 1:
            # 插值计算
            x0, y0 = CurveData[i]
            x1, y1 = CurveData[i + 1]
            PCurveNorm[i, 0] = v
            PCurveNorm[i, 1] = y0 + (v - x0) * (y1 - y0) / (x1 - x0)
        else:
            PCurveNorm[i, 0] = v
            PCurveNorm[i, 1] = PRated  # 防止超出范围的情况

    return PCurveNorm

# 保存结果函数


def save_results(PCurve, PCurveNorm, EPKW, EPPer, outputDir='./output/A01'):
    if not os.path.exists(outputDir):
        os.makedirs(outputDir)
    pd.DataFrame(PCurve, columns=[fieldWindSpeed, fieldActivePower]).to_csv(
        os.path.join(outputDir, 'PCurve.csv'), index=False)
    pd.DataFrame(PCurveNorm, columns=[fieldWindSpeed, fieldActivePower]).to_csv(
        os.path.join(outputDir, 'PCurveNorm.csv'), index=False)
    pd.DataFrame([EPKW], columns=['EPIdealTotal', 'EPActualTotal', 'EPLostStopTotal', 'EPLostBadLimitTotal', 'EPLostPerformTotal', 'EPLostBadTotal',
                 'EPLostLimitTotal', 'EPOverTotal', 'WindSpeedAvr', 'TurbineRunRate']).to_csv(os.path.join(outputDir, 'EPKW.csv'), index=False)
    pd.DataFrame([EPPer], columns=['Percent1', 'Percent2', 'Percent3', 'Percent4', 'Percent5', 'Percent6', 'Percent7',
                 'Percent8', 'WindSpeedAvr', 'TurbineRunRate']).to_csv(os.path.join(outputDir, 'EPPer.csv'), index=False)

# 绘制结果函数，使用 Plotly


def plot_results(PVBad, PVLimit, PVDot, PCurve, IdealCurve):
    fig = go.Figure()

    # 添加坏点数据
    if not PVBad.empty:
        fig.add_trace(go.Scatter(x=PVBad[fieldWindSpeed], y=PVBad[fieldActivePower],
                      mode='markers', name='坏点', marker=dict(color='red')))

    # 添加限电点数据
    if not PVLimit.empty:
        fig.add_trace(go.Scatter(x=PVLimit[fieldWindSpeed], y=PVLimit[fieldActivePower],
                      mode='markers', name='限功率', marker=dict(color='blue')))

    # 添加正常点数据
    if not PVDot.empty:
        fig.add_trace(go.Scatter(x=PVDot[fieldWindSpeed], y=PVDot[fieldActivePower],
                      mode='markers', name='好点', marker=dict(color='black')))

    # 添加实测功率曲线
    if PCurve.shape[0] > 0:
        fig.add_trace(go.Scatter(
            x=PCurve[:, 0], y=PCurve[:, 1], mode='lines+markers', name='实测功率曲线', line=dict(color='green')))

    # 添加设计功率曲线
    if IdealCurve.shape[0] > 0:
        fig.add_trace(go.Scatter(x=IdealCurve[:, 0], y=IdealCurve[:, 1],
                      mode='lines+markers', name='合同功率曲线', line=dict(color='yellow')))

    # 更新布局
    fig.update_layout(
        title={'text':'风力机功率散点数据分类标识','x':0.5},
        xaxis_title=fieldWindSpeed,
        yaxis_title=fieldActivePower,
        legend=dict(x=0.01, y=0.99),
        template='plotly_white'
    )

    # fig.show()
    outputDir = './output/A01'
    filePath = os.path.join(outputDir, f'power_scatter.html')
    fig.write_html(filePath)


# 主函数
def main():
    # 读取数据
    March809, IdealCurve = read_data()
    # 计算统计信息
    AA, BB, PNum, EPActualTotal, WindSpeedAvr, TurbineRunRate, EPIdealTotalAAA = calculate_statistics(
        March809, IdealCurve)
    # 分类数据
    DzMarch809, XBoxNumber = classify_data(March809, PNum, VNum)
    # 计算百分比
    PBoxPercent, VBoxPercent = compute_percentages(XBoxNumber, PNum, VNum)
    # 查找主带
    DotDense, DotDenseLeftRight, PowerLimit, WidthAverage, WidthVar, PowerBandWidth, PBoxMaxIndex = find_main_band(
        PBoxPercent, PNum, VNum, XBoxNumber)
    # 标记坏点
    DzMarch809Sel = mark_bad_points(
        DzMarch809, DotDenseLeftRight, PBoxMaxIndex, PowerLimit, PNum, VNum, XBoxNumber, PBoxPercent)
    # 标识限负荷数据点
    PVLimitArray, DzMarch809Sel = identify_limit_load_data(
        DzMarch809, DzMarch809Sel, PRated)

    # 初始化存储好点和坏点的 DataFrame
    PVBad = pd.DataFrame(columns=DzMarch809.columns)
    PVLimit = pd.DataFrame(columns=DzMarch809.columns)
    PVDot = pd.DataFrame(columns=DzMarch809.columns)

    # 存储好点和坏点
    for i in range(len(DzMarch809)):
        if DzMarch809Sel[i] in [1, 2, 3]:
            if not DzMarch809.iloc[[i]].isna().all().all():
                PVBad = pd.concat(
                    [PVBad, DzMarch809.iloc[[i]]], ignore_index=True)
        elif DzMarch809Sel[i] == 4:
            if not DzMarch809.iloc[[i]].isna().all().all():
                PVLimit = pd.concat(
                    [PVLimit, DzMarch809.iloc[[i]]], ignore_index=True)
        else:
            if not DzMarch809.iloc[[i]].isna().all().all():
                PVDot = pd.concat(
                    [PVDot, DzMarch809.iloc[[i]]], ignore_index=True)

    # 计算能量损失
    EPLostStopTotal, EPLostBadTotal, EPOverTotal, EPLostPerformTotal, EPIdealTotal = calculate_energy_loss(
        DzMarch809Sel, DzMarch809, IdealCurve, PNum, BB, PRated, EPIdealTotalAAA, EPActualTotal)

    EPKW = [EPIdealTotal, EPActualTotal, EPLostStopTotal, EPLostBadTotal,
            EPLostPerformTotal, EPLostBadTotal, 0, EPOverTotal, WindSpeedAvr, TurbineRunRate]
    EPPer = [100, EPActualTotal / EPIdealTotal * 100, EPLostStopTotal / EPIdealTotal * 100, EPLostBadTotal / EPIdealTotal * 100,
             EPLostPerformTotal / EPIdealTotal * 100, EPLostBadTotal / EPIdealTotal * 100, 0, EPOverTotal / EPActualTotal * 100, WindSpeedAvr, TurbineRunRate]

    # 计算实测功率曲线
    PCurve = calculate_measured_power_curve(PVDot, VRated, PRated)
    # 计算标准正则功率曲线
    PCurveNorm = calculate_normalized_power_curve(IdealCurve, VRated, PRated)

    # 保存结果
    save_results(PCurve, PCurveNorm, EPKW, EPPer)
    # 绘制结果
    plot_results(PVBad, PVLimit, PVDot, PCurve, IdealCurve.to_numpy())


if __name__ == "__main__":
    main()
