Files
cma-management/backend/scripts/clean_kpi_data_governance.py
T

142 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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_071id=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()