# -*- coding: utf-8 -*-
"""
DGCUP 2021 A —— 高铁牵引供电系统运行数据分析及等值建模
=====================================================

确定性真源模型（纯标准库，random.seed(2021) 锚定，零外部依赖）。

赛题背景：牵引变电所将 220 kV 三相电压降为 27.5 kV 单相，经牵引网向动车组供电。
动车组的非线性、单相性、冲击性负荷带来电压/电流不平衡与谐波等电能质量问题；
同时牵引网可抽象为戴维南（Thevenin）等效，用于短路/潮流/谐波阻抗分析。

本题练习数据集为**合成测量数据**（仅用于跑通方法，非官方原题实测）：
  24 小时、5 分钟分辨率、共 288 个采样点；
  3 个供电臂（区段 A/B/C）的牵引电流，及其在供电侧合成的 3 相电流；
  母线馈线电压 U（标称 27.5 kV，列车通过时跌落至约 22 kV）、总有功 P、无功 Q、功率因数。

四路数字一致：本文件为唯一真源；fig_*.py 与 papers/*.md 均引用此处产出的数值。

子问题映射（与真实赛题主题一致，数据为本站合成）：
  问题1 数据预处理与运行工况特征：缺失/异常清洗、日负荷曲线、区段负荷分布；
  问题2 电能质量评估：对称分量（正/负/零序）求电流不平衡度、功率因数；
  问题3 谐波与异常检测：FFT 求电流谐波频谱与 THD、基于残差的异常检测（电压暂降/电流冲击）；
  问题4 等值建模：由 (P,Q)-(U,I) 散点最小二乘拟合戴维南 E0、Zeq；谐波阻抗模型 Z_h=R+jhX。

用法：cd tools/ && python3 gen_dgcup2021a.py
"""
import os
import csv
import math
import random

SEED = 2021

HERE = os.path.dirname(os.path.abspath(__file__))
OUT_DIR = os.path.normpath(os.path.join(HERE, "..", "assets", "problems", "data"))

# ---------------- 物理 / 系统常数（合成、量级贴近真实牵引网） ----------------
UNOM = 27.5            # 牵引网标称电压 (kV)
E0_TRUE = 28.5         # 戴维南等效电势幅值 (kV)，角度 0
ZR_TRUE = 1.2          # 等效电阻 (Ω)
ZX_TRUE = 3.5          # 等效电抗 (Ω)
N = 288                # 24h * 12 步/h = 288
DT_MIN = 5             # 采样间隔（分钟）
SECTIONS = ["A", "B", "C"]

# ---------------- 工具：迭代 FFT（radix-2，纯标准库） ----------------
def _fft(x):
    n = len(x)
    # 位反转置换
    j = 0
    out = list(x)
    for i in range(1, n):
        bit = n >> 1
        while j & bit:
            j ^= bit
            bit >>= 1
        j ^= bit
        if i < j:
            out[i], out[j] = out[j], out[i]
    L = 2
    while L <= n:
        ang = -2.0 * math.pi / L
        wr, wi = math.cos(ang), math.sin(ang)
        for i in range(0, n, L):
            r, im = 1.0, 0.0
            half = L // 2
            for k in range(half):
                a = out[i + k]
                b = out[i + k + half]
                tr = b.real * r - b.imag * im
                ti = b.real * im + b.imag * r
                out[i + k] = complex(a.real + tr, a.imag + ti)
                out[i + k + half] = complex(a.real - tr, a.imag - ti)
                nr = r * wr - im * wi
                ni = r * wi + im * wr
                r, im = nr, ni
        L <<= 1
    return out


# ---------------- 日负荷需求轮廓（驱动列车运行密度） ----------------
def _demand(hour):
    """返回 0..1 的负荷强度系数：夜间低、早晚高峰高。"""
    if 0 <= hour < 5:
        return 0.15
    if 5 <= hour < 7:
        return 0.35 + 0.10 * (hour - 5)
    if 7 <= hour < 9.5:
        return 1.0
    if 9.5 <= hour < 16:
        return 0.55
    if 16 <= hour < 19.5:
        return 0.95
    if 19.5 <= hour < 22:
        return 0.5
    return 0.25


# ---------------- 生成各供电臂的列车牵引电流 ----------------
def _train_currents(rng):
    """按日负荷轮廓生成 3 个供电臂的牵引电流序列（A），含基线负荷与梯形脉冲。"""
    I = {s: [0.0] * N for s in SECTIONS}
    for s in SECTIONS:
        base = 120.0 + rng.uniform(0, 80)      # 基线（无车）电流 A
        t = 0
        while t < N:
            hour = t * DT_MIN / 60.0
            p = _demand(hour)
            if rng.random() < 0.55 * p + 0.04:
                dur = rng.randint(2, 6)         # 持续 10~30 min
                peak = rng.uniform(800, 2000)   # 牵引脉冲峰值电流 A
                ramp = 1                         # 1 步爬升，平滑
                for k in range(t, min(t + dur, N)):
                    dk = k - t
                    if dk < ramp:
                        val = base + (peak - base) * (dk + 1) / (ramp + 1)
                    elif dk >= dur - ramp:
                        val = base + (peak - base) * (dur - dk) / (ramp + 1)
                    else:
                        val = peak
                    I[s][k] = max(I[s][k], val)
                t += dur + rng.randint(2, 5)
            else:
                t += 1
        for k in range(N):
            I[s][k] += base
    return I


# ---------------- 对称分量（正/负/零序） ----------------
def _sym_components(ia, ib, ic):
    a = complex(-0.5, math.sqrt(3) / 2.0)   # e^{j120°}
    a2 = a * a
    v0 = (ia + ib + ic) / 3.0
    v1 = (ia + a * ib + a2 * ic) / 3.0
    v2 = (ia + a2 * ib + a * ic) / 3.0
    return v0, v1, v2


# ---------------- 合成运行工况电流波形（用于 FFT / THD） ----------------
def _synth_wave(amps, rng, N=1024, noise=0.0):
    y = [0.0] * N
    for h, A in amps:
        ph = h * 0.3
        for k in range(N):
            y[k] += A * math.sin(2.0 * math.pi * h * k / N + ph)
    if noise > 0:
        for k in range(N):
            y[k] += rng.gauss(0.0, noise)
    return y


def _thd_of(amps_dict, rng):
    """amps_dict: {工况: [(h, A), ...]}；返回 THD(%) 与各工况 top5 谐波。"""
    out = {}
    for cond, amps in amps_dict.items():
        y = _synth_wave(amps, rng, noise=0.004 * amps[0][1])
        Y = _fft([complex(v) for v in y])
        A = [2.0 * abs(Y[h]) / len(Y) for h in range(len(Y))]
        A1 = A[1]
        s = 0.0
        for h in range(2, len(A) // 2):
            s += A[h] ** 2
        thd = math.sqrt(s) / A1 * 100.0 if A1 > 0 else 0.0
        harmonics = sorted(((h, 50.0 * h, A[h]) for h in range(2, len(A) // 2)),
                           key=lambda z: -z[2])[:5]
        out[cond] = (thd, harmonics)
    return out


# ---------------- 谐波阻抗模型拟合 Z_h = R + j*h*X ----------------
def _fit_harmonic_impedance(amps, rng, R0=1.5, X0=4.5):
    """由合成的 (I_h, V_h) 对最小二乘拟合 R, X，其中 V_h=(R+jhX)I_h。"""
    # 构造复数测量
    meas = []
    for h, A in amps:
        if h == 0:
            continue
        Ih = complex(A * math.cos(h * 0.3), A * math.sin(h * 0.3))
        Vh = complex(R0, h * X0) * Ih
        # 加噪声
        Vh = complex(Vh.real + rng.gauss(0, 0.01 * abs(Vh)),
                     Vh.imag + rng.gauss(0, 0.01 * abs(Vh)))
        Ih = complex(Ih.real + rng.gauss(0, 0.005 * A),
                     Ih.imag + rng.gauss(0, 0.005 * A))
        meas.append((h, Ih, Vh))
    # 2x2 正规方程: 未知 R, X
    # ReV = R*Ir - h*X*Ii ; ImV = R*Ii + h*X*Ir
    A11 = A12 = A22 = b1 = b2 = 0.0
    for h, Ih, Vh in meas:
        Ir, Ii = Ih.real, Ih.imag
        # eq1: ReV = R*Ir - h*X*Ii  -> 系数 c1=(Ir, -h*Ii), 残差 ReV - (R*Ir - h*X*Ii)
        # eq2: ImV = R*Ii + h*X*Ir  -> 系数 c2=(Ii,  h*Ir)
        c1r, c1x = Ir, -h * Ii
        c2r, c2x = Ii, h * Ir
        A11 += c1r * c1r + c2r * c2r
        A12 += c1r * c1x + c2r * c2x
        A22 += c1x * c1x + c2x * c2x
        b1 += c1r * Vh.real + c2r * Vh.imag
        b2 += c1x * Vh.real + c2x * Vh.imag
    # 解 2x2
    det = A11 * A22 - A12 * A12
    if abs(det) < 1e-12:
        R, X = R0, X0
    else:
        R = (b1 * A22 - b2 * A12) / det
        X = (A11 * b2 - A12 * b1) / det
    zh_mag = {h: math.sqrt(R * R + (h * X) ** 2) for h in [1, 3, 5, 7, 9, 11]}
    return R, X, zh_mag


# ---------------- 戴维南等效最小二乘拟合 ----------------
def _fit_thevenin(Vmeas, Ic):
    """V = E0 - Z*Ic ; 未知 Er,Ei,Zr,Zi（复数）。
    E0 = V + Z*Ic -> ReE0 = ReV + Zr*ReI - Zi*ImI ; ImE0 = ImV + Zr*ImI + Zi*ReI
    4x4 正规方程 + 高斯消元。"""
    n = len(Vmeas)
    # 累加正规方程矩阵 (4x4) 与右端
    A = [[0.0] * 4 for _ in range(4)]
    b = [0.0] * 4
    # 变量序: [Zr, Zi, Er, Ei]
    cols = []
    for t in range(n):
        Vr, Vi = Vmeas[t].real, Vmeas[t].imag
        Ir, Ii = Ic[t].real, Ic[t].imag
        # 拟合方程 E0 = V + Z*Ic，分量展开：
        # 实部: Er - Vr - (Zr*Ir - Zi*Ii) = 0  ->  [-Ir,  Ii, 1, 0]·x = Vr
        # 虚部: Ei - Vi - (Zr*Ii + Zi*Ir) = 0  ->  [-Ii, -Ir, 0, 1]·x = Vi
        eq = [[-Ir, Ii, 1.0, 0.0, Vr],
              [-Ii, -Ir, 0.0, 1.0, Vi]]
        for e in eq:
            row = e[:4]
            rhs = e[4]
            for r in range(4):
                for c in range(4):
                    A[r][c] += row[r] * row[c]
                b[r] += row[r] * rhs
    # 高斯消元
    for i in range(4):
        p = i + max(range(4 - i), key=lambda k: abs(A[i + k][i]))
        if abs(A[p][i]) < 1e-12:
            continue
        A[i], A[p] = A[p], A[i]
        b[i], b[p] = b[p], b[i]
        pv = A[i][i]
        for j in range(i, 4):
            A[i][j] /= pv
        b[i] /= pv
        for r in range(4):
            if r != i:
                f = A[r][i]
                for j in range(i, 4):
                    A[r][j] -= f * A[i][j]
                b[r] -= f * b[i]
    x = b  # 对角化后 b 即解
    Zr, Zi, Er, Ei = x[0], x[1], x[2], x[3]
    # R²
    ss_res = ss_tot = 0.0
    for t in range(n):
        Vp = complex(Er, Ei) - complex(Zr, Zi) * Ic[t]
        d = abs(Vp - Vmeas[t]) ** 2
        ss_res += d
        ss_tot += abs(Vmeas[t] - complex(sum(v.real for v in Vmeas) / n,
                                         sum(v.imag for v in Vmeas) / n)) ** 2
    r2 = 1.0 - ss_res / ss_tot if ss_tot > 0 else 0.0
    return (Er, Ei), (Zr, Zi), r2


# ---------------- 主入口 ----------------
def gen_dgcup2021a(seed=SEED, out_csv="dgcup2021a.csv"):
    rng = random.Random(seed)

    # 1) 各供电臂电流
    Isec = _train_currents(rng)

    # 2) 各步功率因数与电流相角（滞后）
    cos_phi = [0.0] * N
    phi = [0.0] * N
    for t in range(N):
        cp = rng.uniform(0.80, 0.96)
        cos_phi[t] = cp
        phi[t] = math.acos(cp)

    # 3) 合成真实戴维南电压（复数），叠加噪声 -> 测量复电压 Vm；并计算 P,Q
    E0 = complex(E0_TRUE, 0.0)
    Z = complex(ZR_TRUE, ZX_TRUE)
    Vtrue = [complex(0)] * N
    Vm = [complex(0)] * N          # 测量复电压
    Ic = [complex(0)] * N          # 总有功电流（复数）
    U_clean = [0.0] * N
    P = [0.0] * N
    Q = [0.0] * N
    Itot = [0.0] * N               # 总有功电流幅值 (A)
    for t in range(N):
        It = complex(0.0)
        for s in SECTIONS:
            # 该臂电流幅值 (A) -> kA，电流滞后电压 phi
            mag = Isec[s][t] / 1000.0
            Ics = mag * complex(math.cos(-phi[t]), math.sin(-phi[t]))
            It += Ics
        Vt = E0 - Z * It
        Vtrue[t] = Vt
        # 测量噪声（复电压）
        v = complex(Vt.real + rng.gauss(0, 0.04), Vt.imag + rng.gauss(0, 0.04))
        Icm = complex(It.real + rng.gauss(0, 0.002), It.imag + rng.gauss(0, 0.002))
        Vm[t] = v
        Ic[t] = Icm
        U_clean[t] = abs(v)
        S = v * Icm.conjugate()
        P[t] = S.real
        Q[t] = S.imag
        Itot[t] = abs(Icm) * 1000.0

    # 4) 注入真实异常（在测量复电压上叠加突变，制造残差异常）
    anomaly_steps = [60, 95, 140, 200, 250]
    anomaly_flag = [0] * N
    for ts in anomaly_steps:
        anomaly_flag[ts] = 1
        # 电压暂降 ~3.5 kV（与电流无关的残差）
        mag = abs(Vm[ts])
        Vm[ts] = Vm[ts] * ((mag - 3.5) / mag)
        U_clean[ts] = abs(Vm[ts])
        if ts in (95, 140):
            # 电流冲击：额外叠加电流（不改变 Vtrue 关系，制造大残差）
            Ic[ts] = Ic[ts] + complex(1.2, -0.4)
            mag = abs(Vm[ts])
            Vm[ts] = Vm[ts] * ((mag - 1.0) / mag)
            U_clean[ts] = abs(Vm[ts])

    # 5) 在 raw 序列中注入缺失与离群（清洗演示）
    miss_steps = [30, 110, 170, 230, 265]
    out_steps = [75, 185]
    U_raw = list(U_clean)
    Itot_raw = list(Itot)
    for ts in miss_steps:
        U_raw[ts] = float("nan")
        Itot_raw[ts] = float("nan")
    for ts in out_steps:
        U_raw[ts] += 4.5          # 离群电压尖峰
        Itot_raw[ts] += 1800.0    # 离群电流尖峰

    # 6) 清洗：线性插值缺失 + z-score 裁剪离群
    def _interp(arr):
        out = list(arr)
        for i in range(N):
            if math.isnan(out[i]):
                # 前向/后向找最近有效
                a = b = None
                for k in range(i - 1, -1, -1):
                    if not math.isnan(out[k]):
                        a = k; break
                for k in range(i + 1, N):
                    if not math.isnan(out[k]):
                        b = k; break
                if a is not None and b is not None:
                    out[i] = out[a] + (out[b] - out[a]) * (i - a) / (b - a)
                elif a is not None:
                    out[i] = out[a]
                elif b is not None:
                    out[i] = out[b]
        return out

    U_clean2 = _interp(U_raw)
    Itot_clean = _interp(Itot_raw)
    # z-score 裁剪离群（3σ）
    n_missing = len(miss_steps)
    n_outlier = len(out_steps)
    for arr in (U_clean2, Itot_clean):
        mu = sum(v for v in arr if not math.isnan(v)) / (N - 0)
        sd = math.sqrt(sum((v - mu) ** 2 for v in arr) / N)
        for i in range(N):
            if abs(arr[i] - mu) > 3 * sd:
                # 用邻域均值裁剪
                lo = max(0, i - 2); hi = min(N, i + 3)
                arr[i] = sum(arr[lo:hi]) / (hi - lo)

    # 7) 3 相供电侧电流（标准 3 相电流相量集）与对称分量
    # 将 3 个供电臂电流视为三相导线电流幅值，统一滞后角 -phi，三相相差 120°
    ia = [complex(0)] * N; ib = [complex(0)] * N; ic = [complex(0)] * N
    seq0 = [0.0] * N; seq1 = [0.0] * N; seq2 = [0.0] * N
    unb = [0.0] * N
    A120 = 2.0 * math.pi / 3.0
    for t in range(N):
        ang = -phi[t]
        ia[t] = Isec["A"][t] * complex(math.cos(ang), math.sin(ang))
        ib[t] = Isec["B"][t] * complex(math.cos(ang - A120), math.sin(ang - A120))
        ic[t] = Isec["C"][t] * complex(math.cos(ang - 2 * A120), math.sin(ang - 2 * A120))
        v0, v1, v2 = _sym_components(ia[t], ib[t], ic[t])
        seq0[t] = abs(v0); seq1[t] = abs(v1); seq2[t] = abs(v2)
        unb[t] = (abs(v2) / abs(v1) * 100.0) if abs(v1) > 1e-9 else 0.0

    # 8) 日负荷曲线（按小时聚合）
    hourly_P = [0.0] * 24
    hourly_I = {s: [0.0] * 24 for s in SECTIONS}
    cnt = [0] * 24
    for t in range(N):
        h = t * DT_MIN // 60
        hourly_P[h] += P[t]
        for s in SECTIONS:
            hourly_I[s][h] += Isec[s][t]
        cnt[h] += 1
    for h in range(24):
        if cnt[h]:
            hourly_P[h] /= cnt[h]
            for s in SECTIONS:
                hourly_I[s][h] /= cnt[h]
    peak_P = max(P); peak_idx = P.index(peak_P)
    mean_P = sum(P) / N
    load_factor = mean_P / peak_P

    # 9) 谐波与 THD（三种运行工况）
    amps = {
        "空载": [(1, 80), (3, 12), (5, 6), (7, 3), (9, 1.5), (11, 1.0)],
        "牵引": [(1, 1500), (3, 380), (5, 210), (7, 120), (9, 70), (11, 45), (13, 25)],
        "制动": [(1, 1200), (3, 300), (5, 170), (7, 95), (9, 55), (11, 35), (13, 20)],
    }
    thd_res = _thd_of(amps, rng)
    thd = {k: v[0] for k, v in thd_res.items()}
    harmonics = {k: v[1] for k, v in thd_res.items()}

    # 10) 谐波阻抗拟合
    Rz, Xz, zh_mag = _fit_harmonic_impedance(amps["牵引"], rng)

    # 11) 戴维南等效拟合（用清洗后 U、清洗后总有功电流；复电压保留原始相角）
    Vmeas = []
    Ic_clean = []
    for t in range(N):
        # 由清洗后的总有功电流幅值重建复数电流
        mag = Itot_clean[t] / 1000.0
        Icm = mag * complex(math.cos(-phi[t]), math.sin(-phi[t]))
        Ic_clean.append(Icm)
        # 复电压：清洗后的幅值 + 原始测量相角
        ph = math.atan2(Vm[t].imag, Vm[t].real)
        Vmeas.append(complex(U_clean2[t] * math.cos(ph), U_clean2[t] * math.sin(ph)))
    E0_fit, Z_fit, r2 = _fit_thevenin(Vmeas, Ic_clean)

    # 12) 异常检测：基于戴维南模型残差
    res = [0.0] * N
    for t in range(N):
        Vp = complex(E0_fit[0], E0_fit[1]) - complex(Z_fit[0], Z_fit[1]) * Ic_clean[t]
        res[t] = U_clean2[t] - abs(Vp)
    mu_r = sum(res) / N
    sd_r = math.sqrt(sum((r - mu_r) ** 2 for r in res) / N)
    thresh = 3.0 * sd_r
    detected = [1 if abs(res[t]) > thresh else 0 for t in range(N)]
    tp = sum(1 for t in anomaly_steps if detected[t])
    fp = sum(1 for t in range(N) if detected[t] and anomaly_flag[t] == 0)
    fn = sum(1 for t in anomaly_steps if not detected[t])
    precision = tp / (tp + fp) if (tp + fp) else 0.0
    recall = tp / (tp + fn) if (tp + fn) else 0.0

    # 13) 写 CSV
    os.makedirs(OUT_DIR, exist_ok=True)
    path = os.path.join(OUT_DIR, out_csv)
    with open(path, "w", newline="", encoding="utf-8-sig") as f:
        w = csv.writer(f)
        w.writerow(["t", "time_h", "I_A", "I_B", "I_C", "i_a", "i_b", "i_c",
                    "U_raw_kV", "I_total_raw_A", "P_raw_MW", "Q_raw_MVar", "cos_phi",
                    "U_clean_kV", "I_total_clean_A", "anomaly_flag"])
        for t in range(N):
            hh = t * DT_MIN / 60.0
            def fmt(x):
                return "" if (isinstance(x, float) and math.isnan(x)) else round(x, 4)
            w.writerow([
                t, round(hh, 3),
                round(Isec["A"][t], 2), round(Isec["B"][t], 2), round(Isec["C"][t], 2),
                round(ia[t].real, 2), round(ib[t].real, 2), round(ic[t].real, 2),
                fmt(U_raw[t]), fmt(Itot_raw[t]),
                round(P[t], 4), round(Q[t], 4), round(cos_phi[t], 4),
                round(U_clean2[t], 4), round(Itot_clean[t], 2), anomaly_flag[t],
            ])

    # 14) 汇总 D
    D = dict(
        seed=SEED, N=N, dt_min=DT_MIN, sections=len(SECTIONS), Unom_kV=UNOM,
        E0_true=(E0_TRUE, 0.0), Z_true=(ZR_TRUE, ZX_TRUE),
        n_missing=n_missing, n_outlier=n_outlier,
        hourly_P=hourly_P,
        hourly_I_A=hourly_I["A"], hourly_I_B=hourly_I["B"], hourly_I_C=hourly_I["C"],
        peak_P_MW=peak_P, peak_P_time_h=peak_idx * DT_MIN / 60.0,
        mean_P_MW=mean_P, load_factor=load_factor,
        unb_mean_pct=sum(unb) / N, unb_max_pct=max(unb),
        unb_max_time_h=unb.index(max(unb)) * DT_MIN / 60.0,
        pf_mean=sum(cos_phi) / N, pf_min=min(cos_phi),
        thd=thd, harmonics=harmonics,
        Zh_R=Rz, Zh_X=Xz, Zh_mag=zh_mag,
        E0_fit_kV=E0_fit, Zeq_fit_Ohm=Z_fit, R2=r2,
        n_true_anom=len(anomaly_steps), n_detected=sum(detected),
        tp=tp, fp=fp, fn=fn, precision=precision, recall=recall,
        csv_path=path,
        summary="DGCUP2021A 牵引供电分析：N=288、U_nom=27.5kV、THD(牵引)=%.1f%%、电流不平衡度均值=%.2f%%、"
                "戴维南 E0=%.2fkV Zeq=%.2f+j%.2fΩ R2=%.4f、异常检测 P=%.2f R=%.2f"
                % (thd["牵引"], sum(unb) / N, abs(complex(*E0_fit)), Z_fit[0], Z_fit[1],
                   r2, precision, recall),
    )
    # 时间序列（供配图与附录复用，保证四路一致）
    D["series"] = dict(
        t=list(range(N)),
        time_h=[t * DT_MIN / 60.0 for t in range(N)],
        U_raw=U_raw, U_clean=U_clean2,
        Itot_raw=Itot_raw, Itot_clean=Itot_clean,
        I_A=[Isec["A"][t] for t in range(N)],
        I_B=[Isec["B"][t] for t in range(N)],
        I_C=[Isec["C"][t] for t in range(N)],
        ia=[ia[t].real for t in range(N)],
        ib=[ib[t].real for t in range(N)],
        ic=[ic[t].real for t in range(N)],
        seq0=seq0, seq1=seq1, seq2=seq2,
        unb=unb, P=P, Q=Q, cos_phi=cos_phi,
        res=res, detected=detected, anomaly_flag=anomaly_flag,
        hourly_P=hourly_P,
        hourly_I_A=hourly_I["A"], hourly_I_B=hourly_I["B"], hourly_I_C=hourly_I["C"],
    )
    return D


if __name__ == "__main__":
    D = gen_dgcup2021a()
    print(D["summary"])
    print("缺失/离群清洗: 缺失 %d 处, 离群 %d 处" % (D["n_missing"], D["n_outlier"]))
    print("日负荷: 峰值 P=%.2f MW @ %.1fh, 均值 %.2f MW, 负荷率=%.3f"
          % (D["peak_P_MW"], D["peak_P_time_h"], D["mean_P_MW"], D["load_factor"]))
    print("电流不平衡度: 均值 %.2f%%, 最大 %.2f%% @ %.1fh"
          % (D["unb_mean_pct"], D["unb_max_pct"], D["unb_max_time_h"]))
    print("功率因数: 均值 %.3f, 最小 %.3f" % (D["pf_mean"], D["pf_min"]))
    print("谐波 THD: 空载 %.1f%%, 牵引 %.1f%%, 制动 %.1f%%"
          % (D["thd"]["空载"], D["thd"]["牵引"], D["thd"]["制动"]))
    for cond in ("空载", "牵引", "制动"):
        top = ", ".join("h%d=%.0fA" % (h, A) for h, f, A in D["harmonics"][cond])
        print("  %s 主导谐波: %s" % (cond, top))
    print("谐波阻抗拟合: R=%.3fΩ, X=%.3fΩ; |Z_1|=%.2f |Z_3|=%.2f |Z_5|=%.2f Ω"
          % (D["Zh_R"], D["Zh_X"], D["Zh_mag"][1], D["Zh_mag"][3], D["Zh_mag"][5]))
    print("戴维南等效: E0=(%.3f%+ .3fj) kV, Zeq=(%.3f%+ .3fj) Ω, R2=%.4f"
          % (D["E0_fit_kV"][0], D["E0_fit_kV"][1], D["Zeq_fit_Ohm"][0], D["Zeq_fit_Ohm"][1], D["R2"]))
    print("异常检测: 真实 %d, 检出 %d, TP=%d FP=%d FN=%d, 精确率=%.2f 召回率=%.2f"
          % (D["n_true_anom"], D["n_detected"], D["tp"], D["fp"], D["fn"],
             D["precision"], D["recall"]))
    print("CSV ->", D["csv_path"])
