feat: Bot KPI管理体系 — bot_source字段 + 11个财务Bot KPI + Bot KPI看板

- 新增 bot_source 字段到 kpi_definitions 表(DB迁移 + 模型字段)
- 创建 bot_kpis.py API(GET /api/cma/bot-kpis + POST .../value)
- 种子脚本 seed_finance_bot_kpis.py 插入11个财务Bot KPI
- BotKpiDashboard.vue 看板组件(三区:核心产出5/质量3/用户反馈3)
- 路由 /bot-kpis + 侧边栏菜单入口
- 复用五档评分引擎
This commit is contained in:
Hermes CI Fix
2026-07-25 07:44:41 +08:00
parent c7ec8b2c99
commit abedf8cb8d
8 changed files with 660 additions and 1 deletions
+180
View File
@@ -0,0 +1,180 @@
"""
种子数据:财务Bot KPI11个)
插入到 kpi_definitions 表,bot_source='finance-bot'
"""
import pymysql
import os
import sys
from datetime import datetime
DB_USER = os.getenv("CMA_DB_USER", "cma_user")
DB_PASS = os.getenv("CMA_DB_PASS", "cma_pass_2026")
DB_HOST = os.getenv("CMA_DB_HOST", "127.0.0.1")
DB_PORT = int(os.getenv("CMA_DB_PORT", "3306"))
DB_NAME = os.getenv("CMA_DB_NAME", "cma")
FINANCE_BOT_KPIS = [
# ── 核心产出(5个 · 月度考核)──
{
"kpi_code": "FB_ANALYSIS_COUNT",
"kpi_name": "分析报告产出数",
"formula": "月度生成的结构化分析报告数量",
"unit": "",
"target_value": 20,
"frequency": "monthly",
"category": "core_output",
"weight": 15,
},
{
"kpi_code": "FB_ACCURACY_RATE",
"kpi_name": "数据提取准确率",
"formula": "1(数据错误次数/总分析次数)",
"unit": "%",
"target_value": 98,
"frequency": "monthly",
"category": "core_output",
"weight": 25,
},
{
"kpi_code": "FB_ISSUE_FOUND",
"kpi_name": "问题发现数",
"formula": "月度发现的影响经营的问题数量",
"unit": "",
"target_value": 5,
"frequency": "monthly",
"category": "core_output",
"weight": 20,
},
{
"kpi_code": "FB_ACTION_RATE",
"kpi_name": "行动采纳率",
"formula": "被用户采纳的行动建议数/总建议数",
"unit": "%",
"target_value": 60,
"frequency": "monthly",
"category": "core_output",
"weight": 25,
},
{
"kpi_code": "FB_RESPONSE_TIME",
"kpi_name": "响应时效",
"formula": "用户发文件到出分析结果的平均时间",
"unit": "分钟",
"target_value": 10,
"frequency": "monthly",
"category": "core_output",
"weight": 15,
},
# ── 质量监控(3个 · 月度考核)──
{
"kpi_code": "FB_DATA_GAP",
"kpi_name": "数据间隙发现率",
"formula": "发现的数据异常/缺失数 / 应发现数",
"unit": "%",
"target_value": 90,
"frequency": "monthly",
"category": "quality",
"weight": 30,
},
{
"kpi_code": "FB_CONSISTENCY",
"kpi_name": "跨期一致性",
"formula": "同期指标口径是否一致",
"unit": "%",
"target_value": 100,
"frequency": "monthly",
"category": "quality",
"weight": 30,
},
{
"kpi_code": "FB_CITATION",
"kpi_name": "结论可追溯率",
"formula": "每个结论有对应的数据来源",
"unit": "%",
"target_value": 100,
"frequency": "monthly",
"category": "quality",
"weight": 40,
},
# ── 用户反馈(3个 · 季度考核)──
{
"kpi_code": "FB_SATISFACTION",
"kpi_name": "用户满意度",
"formula": "用户对分析报告的评分(1-5分)",
"unit": "",
"target_value": 4.0,
"frequency": "quarterly",
"category": "user_feedback",
"weight": 40,
},
{
"kpi_code": "FB_REUSE_RATE",
"kpi_name": "复用率",
"formula": "用户连续使用天数/月总天数",
"unit": "%",
"target_value": 80,
"frequency": "quarterly",
"category": "user_feedback",
"weight": 30,
},
{
"kpi_code": "FB_REFERRAL",
"kpi_name": "推荐率",
"formula": "用户主动向他人推荐次数",
"unit": "",
"target_value": 1,
"frequency": "quarterly",
"category": "user_feedback",
"weight": 30,
},
]
def run():
conn = pymysql.connect(
host=DB_HOST, user=DB_USER, password=DB_PASS,
database=DB_NAME, charset="utf8mb4",
)
cursor = conn.cursor()
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
inserted = 0
skipped = 0
for kpi in FINANCE_BOT_KPIS:
code = kpi["kpi_code"]
# 检查是否已存在
cursor.execute("SELECT id FROM kpi_definitions WHERE kpi_code = %s", (code,))
existing = cursor.fetchone()
if existing:
print(f"{code} 已存在 (id={existing[0]})")
skipped += 1
continue
sql = """
INSERT INTO kpi_definitions
(entity_id, kpi_code, kpi_name, dimension, formula, unit,
target_value, frequency, category, status, bot_source, data_source,
data_owner, created_at, updated_at)
VALUES
(%s, %s, %s, %s, %s, %s,
%s, %s, %s, 'active', 'finance-bot', 'Bot自计数',
'FinanceBot', %s, %s)
"""
cursor.execute(sql, (
1, code, kpi["kpi_name"], "process", kpi["formula"], kpi["unit"],
kpi["target_value"], kpi["frequency"], kpi["category"],
now, now,
))
new_id = cursor.lastrowid
print(f"{code} -> id={new_id}")
inserted += 1
conn.commit()
cursor.close()
conn.close()
print(f"\n完成:新增 {inserted} 条,跳过 {skipped} 条(共 {len(FINANCE_BOT_KPIS)} 个KPI")
if __name__ == "__main__":
run()