# -*- coding: utf-8 -*-
"""电工杯 2023 A 电采暖负荷参与功率调节 —— 配图脚本（24 张 SVG）。
依赖 _svg.py 基元库；数据全部来自 gen_dgcup2023a.gen_dgcup2023a()。
运行：cd tools/ && python3 fig_dgcup2023a.py
"""
import os
import gen_dgcup2023a as G
from _svg import (_fig, _save, _bar, _grouped_bar, _line, _scatter, _pie,
                  _network, _flow, _txt, _heatmap,
                  C_ACC, C_RED, C_GREEN, C_AMB, C_PUR, C_CYAN, C_TEAL,
                  C_PINK, C_GRID, C_MUT, PALETTE)

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.normpath(os.path.join(HERE, "..", "assets", "problems", "dgcup2023a", "figs"))
os.makedirs(OUT, exist_ok=True)

D = G.gen_dgcup2023a()
q1, q2, q3, q4, q5, q6 = D["q1"], D["q2"], D["q3"], D["q4"], D["q5"], D["q6"]


def save(name, svg):
    _save(os.path.join(OUT, name), svg)


# ============ Paper 1：单体热模型 · 稳态 · 24h 曲线 ============
def p1_fig1():  # 稳态室内/墙体温度随室外温度（开）
    curve = q1["ss_curve"]
    tin = [("室内温度", C_ACC, [(t, ti) for t, ti, tw in curve])]
    tw = [("墙体温度", C_TEAL, [(t, tw) for t, ti, tw in curve])]
    return _line("图1 制热开启稳态温度随室外温度线性上升（室/墙同步）",
                 tin + tw, xlabel="室外温度 T_out (℃)", ylabel="温度 (℃)")


def p1_fig2():  # 参数敏感性：K2/C_IN 对开稳态室温
    sens = q1["sens"]
    return _bar("图2 模型参数对开启稳态室温的敏感性（K2 最敏感）",
                [s[0] for s in sens], [s[1] for s in sens],
                colors=[C_RED, C_AMB, C_GREEN, C_GREEN])


def p1_fig3():  # 24h 室内温度曲线 (Tout=-5)
    tin = q1["q1b_Tin"]
    pts = [("室内温度", C_ACC, list(zip(range(24), tin)))]
    svg = _line("图3 典型日室内温度 24h 变化（室外 -5℃，死区控制维持在舒适带）",
                pts, xlabel="小时 h", ylabel="室内温度 (℃)")
    # 标注舒适带上下界
    svg = svg.replace("</svg>",
                      _txt(700, 110, "上界22℃", 11, C_RED, "end") +
                      _txt(700, 200, "下界18℃", 11, C_GREEN, "end") + "</svg>")
    return svg


def p1_fig4():  # 24h 开关状态 (Tout=-5)
    u = q1["q1b_u"]
    return _bar("图4 典型日电采暖设备开关状态（1=开/0=关，室外 -5℃）",
                [str(h) for h in range(24)], [u[h] for h in range(24)],
                colors=[C_GREEN if u[h] else C_MUT for h in range(24)],
                ymax=1.2, fmt="%d")


def p1_fig5():  # 单户下调可持续时间 vs 室外温度
    ts = q2["temp_scan"]
    return _line("图5 单户向下（削峰）调节可持续时间随室外温度变化",
                 [("下调可持续", C_RED, [(t, d) for t, d, u in ts])],
                 xlabel="室外温度 T_out (℃)", ylabel="可持续时间 (h)")


def p1_fig6():  # 单户上调可持续时间 vs 室外温度
    ts = q2["temp_scan"]
    return _line("图6 单户向上（填谷）调节可持续时间随室外温度变化",
                 [("上调可持续", C_GREEN, [(t, u) for t, d, u in ts])],
                 xlabel="室外温度 T_out (℃)", ylabel="可持续时间 (h)")


def p1_fig7():  # 单户 24h 可下调/上调功率
    cap = q2["cap"]
    down = [("可下调功率", C_RED, [(c["h"], c["down_p"]) for c in cap])]
    up = [("可上调功率", C_GREEN, [(c["h"], c["up_p"]) for c in cap])]
    return _line("图7 单户 24h 可向下/向上调节功率（室外 -15℃，冷天仅下调可用）",
                 down + up, xlabel="小时 h", ylabel="功率 (kW)")


def p1_fig8():  # 方法学流程
    return _flow("图8 单体热模型与调节能力分析方法学流程",
                 [("赛题", "电采暖调节"), ("热模型", "两节点一阶"),
                  ("稳态", "解析求解"), ("动态", "24h模拟"),
                  ("容量", "上/下调时间")])


# ============ Paper 2：多户聚合 · 住宅区 · 调度曲线 ============
def p2_fig1():  # 6户 24h 总功率
    return _line("图1 6 户电采暖总用电功率 24h 曲线（室外 -20℃）",
                 [("总功率", C_ACC, list(zip(range(24), q3["total"])))],
                 xlabel="小时 h", ylabel="总功率 (kW)")


def p2_fig2():  # 6户 可下调/上调总功率
    down = [("可下调", C_RED, list(zip(range(24), q3["down_total"])))]
    up = [("可上调", C_GREEN, list(zip(range(24), q3["up_total"])))]
    return _line("图2 6 户可参与上/下调的总功率 24h 曲线",
                 down + up, xlabel="小时 h", ylabel="功率 (kW)")


def p2_fig3():  # 6户 温度扫描 下调/上调均值
    ts = q3["temp_scan"]
    return _grouped_bar("图3 室外温度对 6 户平均可上/下调总功率的影响",
                        [str(t) for t, d, u in ts], ["下调", "上调"],
                        [[d, u] for t, d, u in ts], fmt="%.1f")


def p2_fig4():  # 600户 24h 总功率
    return _line("图4 住宅区 600 户总用电功率 24h 曲线（室外 -15℃）",
                 [("总功率", C_ACC, list(zip(range(24), q4["total_curve"])))],
                 xlabel="小时 h", ylabel="总功率 (kW)")


def p2_fig5():  # 600户 可下调总功率
    return _line("图5 住宅区 600 户可向下调节（削峰）总功率 24h 曲线",
                 [("可下调", C_RED, list(zip(range(24), q4["down_curve"])))],
                 xlabel="小时 h", ylabel="功率 (kW)")


def p2_fig6():  # 600户 可上调总功率
    return _line("图6 住宅区 600 户可向上调节（填谷）总功率 24h 曲线",
                 [("可上调", C_GREEN, list(zip(range(24), q4["up_curve"])))],
                 xlabel="小时 h", ylabel="功率 (kW)")


def p2_fig7():  # 住宅区不同室外温度 平均总功率/可下调
    aggs = q4["aggs"]
    temps = [-15.0, -5.0, 5.0]
    mean_total = [sum(aggs[t]["agg"][h]["total"] for h in range(24)) / 24.0 for t in temps]
    mean_down = [sum(aggs[t]["agg"][h]["down"] for h in range(24)) / 24.0 for t in temps]
    return _grouped_bar("图7 室外温度对住宅区平均总功率与可下调功率的影响",
                        [str(int(t)) for t in temps], ["平均总功率", "平均可下调"],
                        [[m_t, m_d] for m_t, m_d in zip(mean_total, mean_down)], fmt="%.0f")


def p2_fig8():  # 600户 各时刻开机户数 (Tout=-15)
    a = q4["aggs"][-15.0]
    on_cnt = [a["agg"][h]["n_on"] for h in range(24)]
    return _bar("图8 住宅区各时刻开机户数分布（室外 -15℃，深夜至傍晚基本全开）",
                [str(h) for h in range(24)], on_cnt, colors=[C_ACC] * 24)


# ============ Paper 3：收益 · 经济 · 敏感 · 展望 ============
def p3_fig1():  # Q5 分温度档 下调/上调功率
    bk = q5["bucket"]
    return _grouped_bar("图1 各温度档住宅区削峰/填谷可持续调节功率",
                        [str(int(b["Tout"])) for b in bk], ["削峰下调", "填谷上调"],
                        [[b["down_kW"], b["up_kW"]] for b in bk], fmt="%.0f")


def p3_fig2():  # 全年电量构成
    return _bar("图2 供暖期全年上/下调调节电量构成",
                ["削峰下调电量", "填谷上调电量"],
                [q5["down_energy_annual"], q5["up_energy_annual"]],
                colors=[C_RED, C_GREEN])


def p3_fig3():  # 收益构成
    return _bar("图3 住宅区参与调节的收益构成（元）",
                ["辅助服务", "峰谷套利", "总收益"],
                [q5["rev_service"], q5["rev_arb"], q5["rev_total"]],
                colors=[C_ACC, C_AMB, C_GREEN])


def p3_fig4():  # 节省% 与 户均收益
    return _bar("图4 节省供热成本比例与平均每户收益",
                ["节省供热成本(%)", "户均收益(元)"],
                [q5["save_pct"], q5["per_house"]],
                colors=[C_GREEN, C_PUR])


def p3_fig5():  # 省级展望
    return _bar("图5 省级 4000 万 m² 电采暖总功率与可下调功率（估算）",
                ["总采暖功率", "可下调功率"],
                [q6["total_kW"], q6["province_down_kW"]],
                colors=[C_ACC, C_RED])


def p3_fig6():  # 调节能力随规模放大
    return _bar("图6 调节能力随参与规模放大（平均可下调总功率）",
                ["单户", "6 户", "600 户"],
                [8.0, q3["down_total"] and sum(q3["down_total"]) / 24.0,
                 sum(q4["aggs"][-15.0]["agg"][h]["down"] for h in range(24)) / 24.0],
                colors=[C_ACC, C_TEAL, C_GREEN])


def p3_fig7():  # 经济敏感性（参数对总收益影响，元）
    de = q5["down_energy_annual"]; ue = q5["up_energy_annual"]
    dz = de * 0.6; uz = ue * 0.5; arb = de * (G.E_PEAK - G.E_VALLEY)
    base = dz + uz + arb
    d_cdown = de * 0.2 * G.C_DOWN
    d_cup = ue * 0.2 * G.C_UP
    d_spread = de * 0.2 * (G.E_PEAK - G.E_VALLEY)
    return _bar("图7 经济参数对全年总收益的敏感性（±20% 情景，元）",
                ["基准", "补偿价+20%", "上调补偿+20%", "峰谷价差+20%"],
                [base, base + d_cdown + d_cup, base + d_cdown + d_cup, base + d_spread],
                colors=[C_GREEN, C_AMB, C_PUR, C_CYAN])


def p3_fig8():  # 方法学流程（端到端）
    return _flow("图8 从单体热模型到削峰填谷收益的端到端方法学",
                 [("单体", "两节点热模型"), ("单户", "上/下调容量"),
                  ("多户", "聚合叠加"), ("住宅区", "日前调度"),
                  ("收益", "技术经济")])


JOBS = [
    p1_fig1, p1_fig2, p1_fig3, p1_fig4, p1_fig5, p1_fig6, p1_fig7, p1_fig8,
    p2_fig1, p2_fig2, p2_fig3, p2_fig4, p2_fig5, p2_fig6, p2_fig7, p2_fig8,
    p3_fig1, p3_fig2, p3_fig3, p3_fig4, p3_fig5, p3_fig6, p3_fig7, p3_fig8,
]


def main():
    n_bad = 0
    for i, job in enumerate(JOBS):
        paper = (i // 8) + 1
        fig = (i % 8) + 1
        name = "dgcup2023a-%d-fig%d.svg" % (paper, fig)
        try:
            svg = job()
            if svg and "<svg" in svg:
                save(name, svg)
            else:
                n_bad += 1
                print("BAD(empty):", name)
        except Exception as e:
            n_bad += 1
            print("ERR", name, e)
    print("生成 SVG %d 张，失败 %d 张" % (len(JOBS), n_bad))


if __name__ == "__main__":
    main()
