#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CMA 数据治理 P0-2 数据清洗脚本（2026-08-15）
================================================
背景: CMA 系统数据治理 P0 任务, 财务Bot已拍板执行 P0-2 数据清洗。

操作清单:
  1. 备份 kpi_values -> kpi_values_bak_20260815（先备份再动数据）
  2. 删除异常周期数据: source_batch='986360a37b99'（26条, id 1323-1348,
     period 列是金额乱码: 2459.2/1021.21/1016.12/71981.55/103833.02/nan 等, 非有效期间）
  3. 176条 active 且 target_value=0 的 KPI: formula_desc 追加【目标值待补充】（不删除）。
     ⚠️ 任务书 SQL 写 SET remark=..., 但 kpi_definitions 表无 remark 列（已 SHOW COLUMNS 核实）;
     实际落点为 formula_desc —— 经查该 176 条 formula_desc 全部非空且为说明文字
     （如 EXT_071"待财务提供旧科目表核对归属"），是事实上的备注/说明字段。
  4. EXT_071 (id=196, kpi_name=未命名科目(历史导入数据), entity_id=1):
     历史Excel导入残留(source_batch=0720ca516636) -> 归档 status='archived',
     其 kpi_values 数据保留不动
  5. FB_ ×11(process维度) / BH_SALES_ ×10(finance维度) 编码保留不改（只加注释）:
     - FB_      = 财务Bot自身工作KPI, 涉及前端引用, 改码会破坏财务Bot链路
     - BH_SALES_ = 博海销售真实数据, 涉及前端引用
     - 本脚本仅输出只读清单确认未改动

约束（铁律）:
  - 不删除 kpi_definitions 任何行; 不删除 EXT_071 的 kpi_values
  - 可重复执行（幂等）: 备份表先 DROP IF EXISTS 再重建; 其余操作以 WHERE 条件天然幂等

用法: python3 clean_kpi_data_governance.py
"""
import pymysql


def main():
    conn = pymysql.connect(host="127.0.0.1", port=3306, user="cma_user",
                           password="cma_pass_2026", database="cma", charset="utf8mb4")
    cur = conn.cursor(pymysql.cursors.DictCursor)

    def q(sql, args=None):
        cur.execute(sql, args)
        return cur.fetchall()

    def c(sql, args=None):
        return q(sql, args)[0]["c"]

    print("=" * 64)
    print("CMA 数据治理 P0-2 数据清洗 开始")
    print("=" * 64)

    # ---- 步骤1: 备份 kpi_values（先备份再动数据） ----
    print("\n[步骤1] 备份 kpi_values -> kpi_values_bak_20260815")
    before = c("SELECT COUNT(*) AS c FROM kpi_values")
    print(f"  备份前 kpi_values 总行数: {before}")
    cur.execute("DROP TABLE IF EXISTS kpi_values_bak_20260815")
    cur.execute("CREATE TABLE kpi_values_bak_20260815 AS SELECT * FROM kpi_values")
    bak = c("SELECT COUNT(*) AS c FROM kpi_values_bak_20260815")
    print(f"  备份表 kpi_values_bak_20260815 行数: {bak}（应等于 {before}）")
    assert bak == before, "备份行数不一致, 中止!"
    conn.commit()

    # ---- 步骤2: 删除异常周期数据 ----
    print("\n[步骤2] 删除异常周期数据 source_batch='986360a37b99'")
    rows = q("SELECT id, period FROM kpi_values WHERE source_batch='986360a37b99' ORDER BY id")
    print(f"  删除前命中 {len(rows)} 条", end="")
    if rows:
        print(f"（id {rows[0]['id']} ~ {rows[-1]['id']}）")
        for r in rows:
            print(f"    id={r['id']}  period={r['period']!r}")
    else:
        print(" —— 该批次已无数据（可能上次执行已删除）, 跳过打印")
    cur.execute("DELETE FROM kpi_values WHERE source_batch='986360a37b99'")
    remain = c("SELECT COUNT(*) AS c FROM kpi_values WHERE source_batch='986360a37b99'")
    total_after = c("SELECT COUNT(*) AS c FROM kpi_values")
    print(f"  删除后该批次剩余 {remain} 条（应为 0）")
    print(f"  kpi_values 总行数: {before} -> {total_after}（应减少 {len(rows)}）")
    conn.commit()

    # ---- 步骤3: active 且 target_value=0 标注【目标值待补充】 ----
    print("\n[步骤3] active 且 target_value=0 的 KPI 标注『目标值待补充』（落点 formula_desc）")
    n = c("SELECT COUNT(*) AS c FROM kpi_definitions WHERE status='active' AND target_value=0")
    print(f"  命中 {n} 条（任务书预期 176）")
    assert n == 176, f"与任务书预期 176 不符（实际 {n}）, 中止!"
    already = c("SELECT COUNT(*) AS c FROM kpi_definitions WHERE status='active' AND target_value=0 AND formula_desc LIKE '%【目标值待补充】%'")
    print(f"  已带标记 {already} 条（防重复追加）")
    if already == 0:
        cur.execute("""
            UPDATE kpi_definitions
            SET formula_desc = CONCAT(IFNULL(NULLIF(formula_desc,''),''), '【目标值待补充】')
            WHERE status='active' AND target_value=0
        """)
        print(f"  UPDATE 影响行数: {cur.rowcount}")
    else:
        print(f"  已存在标记 {already} 条, 跳过追加（幂等）")
    marked = c("SELECT COUNT(*) AS c FROM kpi_definitions WHERE status='active' AND target_value=0 AND formula_desc LIKE '%【目标值待补充】%'")
    n_after = c("SELECT COUNT(*) AS c FROM kpi_definitions WHERE status='active' AND target_value=0")
    print(f"  标注后带标记 {marked} 条; active 且 target_value=0 仍为 {n_after} 条（只标注, 不删除）")
    conn.commit()

    # ---- 步骤4: EXT_071 归档 ----
    print("\n[步骤4] EXT_071（id=196）归档 status='archived'")
    ext_before = q("SELECT id,kpi_code,kpi_name,entity_id,status FROM kpi_definitions WHERE id=196 AND kpi_code='EXT_071'")
    print(f"  归档前: {ext_before}")
    cur.execute("UPDATE kpi_definitions SET status='archived' WHERE id=196 AND kpi_code='EXT_071'")
    print(f"  UPDATE 影响行数: {cur.rowcount}")
    ext_after = q("SELECT id,kpi_code,kpi_name,entity_id,status FROM kpi_definitions WHERE id=196 AND kpi_code='EXT_071'")
    print(f"  归档后: {ext_after}")
    kv = c("SELECT COUNT(*) AS c FROM kpi_values WHERE kpi_id=196")
    print(f"  EXT_071 关联 kpi_values 行数（保留不动）: {kv}")
    # 只读引用检查（不修改; 引用表不存在则跳过）
    for label, sql in (
        ("kpi_causality", "SELECT COUNT(*) AS c FROM kpi_causality WHERE source_kpi_id=196 OR target_kpi_id=196"),
        ("kpi_alerts",    "SELECT COUNT(*) AS c FROM kpi_alerts WHERE kpi_id=196"),
        ("alert_rules",   "SELECT COUNT(*) AS c FROM alert_rules WHERE kpi_id=196"),
        ("kpi_hierarchy", "SELECT COUNT(*) AS c FROM kpi_hierarchy WHERE parent_kpi_id=196 OR child_kpi_id=196"),
    ):
        try:
            print(f"  引用表 {label}: {c(sql)} 条（只读检查）")
        except pymysql.err.ProgrammingError as e:
            print(f"  引用表 {label}: 表不存在, 跳过（{e}）")
    conn.commit()

    # ---- 步骤5: FB_/BH_SALES_ 编码保留（不改） ----
    print("\n[步骤5] FB_/BH_SALES_ 编码保留（不改）— 只读确认")
    print("  保留原因: FB_=财务Bot自身工作KPI; BH_SALES_=博海销售真实数据; 均涉及前端引用")
    fb = q("SELECT kpi_code,kpi_name,entity_id,dimension,status FROM kpi_definitions WHERE kpi_code LIKE 'FB\\_%' ORDER BY kpi_code")
    bh = q("SELECT kpi_code,kpi_name,entity_id,dimension,status FROM kpi_definitions WHERE kpi_code LIKE 'BH\\_%' ORDER BY kpi_code")
    print(f"  FB_ 共 {len(fb)} 条:")
    for r in fb:
        print(f"    {r['kpi_code']} | {r['kpi_name']} | entity={r['entity_id']} | {r['dimension']} | {r['status']}")
    print(f"  BH_SALES_ 共 {len(bh)} 条:")
    for r in bh:
        print(f"    {r['kpi_code']} | {r['kpi_name']} | entity={r['entity_id']} | {r['dimension']} | {r['status']}")
    print("  本轮未对 FB_/BH_SALES_ 做任何修改")

    print("\n" + "=" * 64)
    print("P0-2 数据清洗完成（步骤1-4 已提交; 步骤5 仅只读）")
    print("=" * 64)
    conn.close()


if __name__ == "__main__":
    main()
