355 lines
13 KiB
Python
355 lines
13 KiB
Python
"""
|
|
KPI字典库优化P0阶段 — 批量脚本
|
|
涵盖任务1-6: 激活模板KPI + 新增KPI + 修复员工满意度
|
|
"""
|
|
import sys, os
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
|
|
from app.database import get_engine
|
|
from sqlalchemy import text
|
|
from datetime import datetime
|
|
|
|
engine = get_engine()
|
|
|
|
def log(msg):
|
|
print(f"[{datetime.now():%H:%M:%S}] {msg}")
|
|
|
|
def insert_kpi(conn, **kw):
|
|
"""安全插入KPI(检查唯一性)"""
|
|
code = kw["kpi_code"]
|
|
exists = conn.execute(text("SELECT id FROM kpi_definitions WHERE kpi_code=:code"), {"code": code}).fetchone()
|
|
if exists:
|
|
log(f" ⏭️ 已存在: {code} (id={exists[0]})")
|
|
return exists[0], False
|
|
cols = ", ".join(kw.keys())
|
|
vals = ", ".join(f":{k}" for k in kw.keys())
|
|
conn.execute(text(f"INSERT INTO kpi_definitions ({cols}) VALUES ({vals})"), kw)
|
|
new_id = conn.execute(text("SELECT LAST_INSERT_ID()")).scalar()
|
|
log(f" ✅ 新增: {code} (id={new_id})")
|
|
return new_id, True
|
|
|
|
with engine.connect() as conn:
|
|
print("=" * 70)
|
|
log("开始KPI字典库优化P0 — 任务1~6")
|
|
print("=" * 70)
|
|
|
|
# =========================================================
|
|
# 任务1: 激活4个系统模板KPI
|
|
# =========================================================
|
|
log("\n【任务1】激活系统模板KPI")
|
|
print("-" * 50)
|
|
|
|
# 从kpi_templates读取模板数据
|
|
templates = conn.execute(text(
|
|
"SELECT id, kpi_code, kpi_name, dimension, category, formula, formula_desc, "
|
|
"unit, target_value, description FROM kpi_templates WHERE kpi_code IN "
|
|
"('F_ASSET_TURNOVER', 'F_REVENUE_GROWTH', 'L_EMPLOYEE_TURNOVER', 'L_TECH_COVERAGE')"
|
|
)).fetchall()
|
|
|
|
for t in templates:
|
|
tid, code, name, dim, cat, formula, fdesc, unit, target, desc = t
|
|
nid, is_new = insert_kpi(conn,
|
|
template_id=tid,
|
|
is_system=1,
|
|
kpi_code=code,
|
|
kpi_name=name,
|
|
dimension=dim,
|
|
category=cat,
|
|
formula=formula or "",
|
|
formula_desc=fdesc or desc or "",
|
|
unit=unit or "%",
|
|
target_value=target,
|
|
data_source_type="manual",
|
|
frequency="monthly",
|
|
status="active",
|
|
epic="Epic2",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
if is_new:
|
|
# 设置阈值
|
|
if code == "F_ASSET_TURNOVER":
|
|
conn.execute(text("UPDATE kpi_definitions SET threshold_green='>=1.0', threshold_yellow='>=0.8', threshold_red='<0.8' WHERE id=:id"), {"id": nid})
|
|
elif code == "F_REVENUE_GROWTH":
|
|
conn.execute(text("UPDATE kpi_definitions SET threshold_green='>=20', threshold_yellow='>=15', threshold_red='<15' WHERE id=:id"), {"id": nid})
|
|
elif code == "L_EMPLOYEE_TURNOVER":
|
|
conn.execute(text("UPDATE kpi_definitions SET threshold_green='<=5', threshold_yellow='<=10', threshold_red='>10' WHERE id=:id"), {"id": nid})
|
|
elif code == "L_TECH_COVERAGE":
|
|
conn.execute(text("UPDATE kpi_definitions SET threshold_green='>=90', threshold_yellow='>=80', threshold_red='<80' WHERE id=:id"), {"id": nid})
|
|
# 更新模板使用计数
|
|
conn.execute(text("UPDATE kpi_templates SET usage_count = IFNULL(usage_count,0)+1 WHERE id=:id"), {"id": tid})
|
|
log(f" 阈值已配置")
|
|
|
|
# =========================================================
|
|
# 任务2: 新增6个财务KPI
|
|
# =========================================================
|
|
log("\n【任务2】新增6个财务KPI")
|
|
print("-" * 50)
|
|
|
|
finance_kpis = [
|
|
("F_CURRENT_RATIO", "流动比率", "finance", "cash_risk",
|
|
"流动资产/流动负债", "%", 200.0, ">=200", ">=150", "<150", "erp"),
|
|
("F_QUICK_RATIO", "速动比率", "finance", "cash_risk",
|
|
"(流动资产-存货)/流动负债", "%", 100.0, ">=100", ">=80", "<80", "erp"),
|
|
("F_INV_DAYS", "存货周转天数", "finance", "asset_efficiency",
|
|
"365/存货周转率", "天", 45.0, "<=30", "<=45", ">45", "erp"),
|
|
("F_ROI", "总资产报酬率(ROI)", "finance", "profitability",
|
|
"净利润/平均总资产*100", "%", 8.0, ">=12", ">=8", "<8", "erp"),
|
|
("F_QUALITY_RATE", "产品合格率", "finance", "delivery_quality",
|
|
"正品数/总产量*100", "%", 98.0, ">=99", ">=98", "<98", "erp"),
|
|
("F_REWORK_RATE", "返工率", "finance", "delivery_quality",
|
|
"返工工时/总工时*100", "%", 5.0, "<=3", "<=5", ">5", "erp"),
|
|
]
|
|
|
|
for code, name, dim, cat, formula, unit, target, tg, ty, tr, src in finance_kpis:
|
|
nid, is_new = insert_kpi(conn,
|
|
is_system=0,
|
|
kpi_code=code,
|
|
kpi_name=name,
|
|
dimension=dim,
|
|
category=cat,
|
|
formula=formula,
|
|
formula_desc=f"CMA标准{name}指标",
|
|
unit=unit,
|
|
target_value=target,
|
|
threshold_green=tg,
|
|
threshold_yellow=ty,
|
|
threshold_red=tr,
|
|
data_source_type=src,
|
|
frequency="monthly",
|
|
status="active",
|
|
epic="Epic2",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
# =========================================================
|
|
# 任务3: 新增3个客户KPI
|
|
# =========================================================
|
|
log("\n【任务3】新增3个客户KPI")
|
|
print("-" * 50)
|
|
|
|
# 激活C_CUSTOMER_CONCENTRATION模板
|
|
t = conn.execute(text(
|
|
"SELECT id, kpi_code, kpi_name, dimension, category, formula, formula_desc, "
|
|
"unit, target_value, description FROM kpi_templates WHERE kpi_code='C_CUSTOMER_CONCENTRATION'"
|
|
)).fetchone()
|
|
if t:
|
|
nid, is_new = insert_kpi(conn,
|
|
template_id=t[0],
|
|
is_system=1,
|
|
kpi_code="C_CUST_CONCENTRATION",
|
|
kpi_name="客户集中度",
|
|
dimension=t[3],
|
|
category=t[4],
|
|
formula=t[5] or "",
|
|
formula_desc=t[6] or t[9] or "",
|
|
unit=t[7] or "%",
|
|
target_value=t[8],
|
|
data_source_type="erp",
|
|
frequency="monthly",
|
|
status="active",
|
|
epic="Epic2",
|
|
threshold_green="<=20",
|
|
threshold_yellow="<=30",
|
|
threshold_red=">30",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
if is_new:
|
|
conn.execute(text("UPDATE kpi_templates SET usage_count = IFNULL(usage_count,0)+1 WHERE id=:id"), {"id": t[0]})
|
|
|
|
# C_RETENTION_RATE
|
|
insert_kpi(conn,
|
|
is_system=0,
|
|
kpi_code="C_RETENTION_RATE",
|
|
kpi_name="客户保留率",
|
|
dimension="customer",
|
|
category="customer_scale",
|
|
formula="续约客户数/总客户数*100",
|
|
formula_desc="CMA标准客户保留率指标,反映客户粘性",
|
|
unit="%",
|
|
target_value=85.0,
|
|
threshold_green=">=90",
|
|
threshold_yellow=">=85",
|
|
threshold_red="<85",
|
|
data_source_type="erp",
|
|
frequency="monthly",
|
|
status="active",
|
|
epic="Epic2",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
# C_CAC — 注意C_CAC可能已存在(reset_kpi_dict中有定义),改用C_ACQUISITION_COST
|
|
insert_kpi(conn,
|
|
is_system=0,
|
|
kpi_code="C_ACQUISITION_COST",
|
|
kpi_name="获客成本(CAC)",
|
|
dimension="customer",
|
|
category="customer_scale",
|
|
formula="营销费用/新客户数",
|
|
formula_desc="CMA标准客户获取成本指标",
|
|
unit="元",
|
|
target_value=None,
|
|
threshold_green="<=500",
|
|
threshold_yellow="<=800",
|
|
threshold_red=">800",
|
|
data_source_type="erp",
|
|
frequency="monthly",
|
|
status="active",
|
|
epic="Epic2",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
# =========================================================
|
|
# 任务4: 新增2个流程KPI
|
|
# =========================================================
|
|
log("\n【任务4】新增2个流程KPI")
|
|
print("-" * 50)
|
|
|
|
insert_kpi(conn,
|
|
is_system=0,
|
|
kpi_code="P_PASS_RATE",
|
|
kpi_name="过程合格率",
|
|
dimension="process",
|
|
category="delivery_quality",
|
|
formula="过程合格批次数/总检验批次数*100",
|
|
formula_desc="流程层关注过程质量合格率,区别于财务层产品合格率",
|
|
unit="%",
|
|
target_value=95.0,
|
|
threshold_green=">=98",
|
|
threshold_yellow=">=95",
|
|
threshold_red="<95",
|
|
data_source_type="erp",
|
|
frequency="monthly",
|
|
status="active",
|
|
epic="Epic2",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
insert_kpi(conn,
|
|
is_system=0,
|
|
kpi_code="P_REWORK_RATE",
|
|
kpi_name="流程返工率",
|
|
dimension="process",
|
|
category="delivery_quality",
|
|
formula="返工批次/总生产批次*100",
|
|
formula_desc="流程层返工率,体现过程质量控制水平",
|
|
unit="%",
|
|
target_value=5.0,
|
|
threshold_green="<=3",
|
|
threshold_yellow="<=5",
|
|
threshold_red=">5",
|
|
data_source_type="erp",
|
|
frequency="monthly",
|
|
status="active",
|
|
epic="Epic2",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
# =========================================================
|
|
# 任务5: 新增2个学习KPI
|
|
# =========================================================
|
|
log("\n【任务5】新增2个学习KPI")
|
|
print("-" * 50)
|
|
|
|
# 激活L_TRAINING_HOURS模板
|
|
t = conn.execute(text(
|
|
"SELECT id, kpi_code, kpi_name, dimension, category, formula, formula_desc, "
|
|
"unit, target_value, description FROM kpi_templates WHERE kpi_code='L_TRAINING_HOURS'"
|
|
)).fetchone()
|
|
if t:
|
|
nid, is_new = insert_kpi(conn,
|
|
template_id=t[0],
|
|
is_system=1,
|
|
kpi_code="L_TRAINING_HOURS",
|
|
kpi_name="人均培训时长",
|
|
dimension=t[3],
|
|
category=t[4],
|
|
formula=t[5] or "",
|
|
formula_desc=t[6] or t[9] or "",
|
|
unit=t[7] or "小时",
|
|
target_value=t[8],
|
|
data_source_type="manual",
|
|
frequency="monthly",
|
|
status="active",
|
|
epic="Epic2",
|
|
threshold_green=">=40",
|
|
threshold_yellow=">=20",
|
|
threshold_red="<20",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
if is_new:
|
|
conn.execute(text("UPDATE kpi_templates SET usage_count = IFNULL(usage_count,0)+1 WHERE id=:id"), {"id": t[0]})
|
|
|
|
# L_COMPETENCY
|
|
insert_kpi(conn,
|
|
is_system=0,
|
|
kpi_code="L_COMPETENCY",
|
|
kpi_name="关键岗位胜任度",
|
|
dimension="learning",
|
|
category="talent_pipeline",
|
|
formula="胜任评估得分≥80分人数/关键岗位总人数*100",
|
|
formula_desc="CMA标准关键岗位胜任度指标,通过胜任力评估获取",
|
|
unit="%",
|
|
target_value=85.0,
|
|
threshold_green=">=90",
|
|
threshold_yellow=">=85",
|
|
threshold_red="<85",
|
|
data_source_type="manual",
|
|
frequency="quarterly",
|
|
status="active",
|
|
epic="Epic2",
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
|
|
# =========================================================
|
|
# 任务6: 修复员工满意度阈值
|
|
# =========================================================
|
|
log("\n【任务6】修复员工满意度阈值")
|
|
print("-" * 50)
|
|
|
|
sat = conn.execute(text("SELECT id FROM kpi_definitions WHERE kpi_code='L_EMPLOYEE_SAT'")).fetchone()
|
|
if sat:
|
|
conn.execute(text(
|
|
"UPDATE kpi_definitions SET threshold_green='>=80', threshold_yellow='>=60', threshold_red='<60' WHERE id=:id"
|
|
), {"id": sat[0]})
|
|
log(f" ✅ 员工满意度(id={sat[0]}) 阈值已设置: 绿>=80, 黄>=60, 红<60")
|
|
else:
|
|
log(" ⚠️ 员工满意度KPI不存在")
|
|
|
|
# =========================================================
|
|
# 提交事务
|
|
# =========================================================
|
|
conn.commit()
|
|
|
|
# 统计
|
|
total = conn.execute(text("SELECT COUNT(*) FROM kpi_definitions WHERE status='active'")).scalar()
|
|
by_dim = conn.execute(text(
|
|
"SELECT dimension, COUNT(*) FROM kpi_definitions WHERE status='active' GROUP BY dimension ORDER BY dimension"
|
|
)).fetchall()
|
|
no_threshold = conn.execute(text(
|
|
"SELECT kpi_code, kpi_name FROM kpi_definitions WHERE status='active' AND (threshold_green IS NULL OR threshold_yellow IS NULL OR threshold_red IS NULL)"
|
|
)).fetchall()
|
|
|
|
print("\n" + "=" * 70)
|
|
log(f"✅ 任务1~6完成!KPI字典共 {total} 条")
|
|
for d, c in by_dim:
|
|
print(f" {d}: {c} 条")
|
|
if no_threshold:
|
|
print(f"\n⚠️ 以下KPI仍缺阈值:")
|
|
for code, name in no_threshold:
|
|
print(f" {code}: {name}")
|
|
else:
|
|
print("\n✅ 所有KPI均有阈值配置")
|
|
print("=" * 70)
|