""" bot-bridge 推送脚本 — 财务Bot分析结果自动回填CMA KPI值 + 触发预警 数据通道: 财务Bot MPM结果 → POST /api/cma/bot-bridge/kpi-result → KPIValue写入 → run_alert_check 触发预警 → 联动行动计划 用法: # 1) 推送MPM分析结果(自动映射MPM字段→KPI编码,逐项调kpi-result) python3 bot_bridge_push.py --entity-id 1 --period 2026-08 \ --mpm '{"gross_margin_adjusted": 22.5, "channel_rebate_rate": 78.3}' # 2) 从JSON文件读取MPM结果 python3 bot_bridge_push.py --entity-id 1 --period 2026-08 --mpm-file mpm_result.json # 3) 单KPI直接回填 python3 bot_bridge_push.py --entity-id 1 --period 2026-08 \ --kpi-code C_REBATE_RATE --value 78.3 --source finance-bot # 4) 测试连通性 python3 bot_bridge_push.py --ping 环境变量: CMA_API=http://127.0.0.1:8010/api/cma/bot-bridge (默认) CMA_BRIDGE_TOKEN=cma-bot-bridge-2026 (默认, 与migrate_bot_bridge_v2.sql一致) """ import argparse import json import os import sys from datetime import datetime from typing import Optional import httpx CMA_API = os.getenv("CMA_API", "http://127.0.0.1:8010/api/cma/bot-bridge") BRIDGE_TOKEN = os.getenv("CMA_BRIDGE_TOKEN", "cma-bot-bridge-2026") # MPM字段 → KPI编码映射(与 bot_bridge_v2.MPM_TO_KPI_MAP 保持一致) MPM_TO_KPI_MAP = { "gross_margin_adjusted": "F_GROSS_MARGIN", "net_profit_adjusted": "F_NET_PROFIT", "channel_rebate_rate": "C_REBATE_RATE", "mgmt_expense_ratio": "F_COST_RATIO", } _HEADERS = {"X-BRIDGE-TOKEN": BRIDGE_TOKEN, "Content-Type": "application/json"} def push_kpi_result(entity_id: int, period: str, kpi_code: str, value, source: str = "finance-bot", remark: Optional[str] = None) -> dict: """调用 POST /api/cma/bot-bridge/kpi-result""" payload = { "entity_id": entity_id, "kpi_code": kpi_code, "period": period, "value": value, "source": source, } if remark: payload["remark"] = remark resp = httpx.post(f"{CMA_API}/kpi-result", json=payload, headers=_HEADERS, timeout=30) if resp.status_code >= 400: try: detail = resp.json().get("detail", resp.text) except Exception: detail = resp.text return {"kpi_code": kpi_code, "success": False, "error": detail} return {"kpi_code": kpi_code, "success": True, "data": resp.json()} def push_mpm_results(entity_id: int, period: str, results: dict, source: str = "finance-bot") -> dict: """MPM结果自动回填:映射字段→KPI编码,逐项推送""" summary = {"kpi_pushed": 0, "kpi_failed": 0, "alerts": [], "items": []} for field, kpi_code in MPM_TO_KPI_MAP.items(): if not kpi_code or results.get(field) is None: continue result = push_kpi_result( entity_id, period, kpi_code, results[field], source, remark=f"MPM自动回填[{field}] {datetime.now().strftime('%Y-%m-%d %H:%M')}", ) summary["items"].append(result) if result["success"]: summary["kpi_pushed"] += 1 summary["alerts"].extend(result["data"].get("new_alerts", [])) else: summary["kpi_failed"] += 1 return summary def main(): parser = argparse.ArgumentParser(description="财务Bot分析结果回填CMA") parser.add_argument("--entity-id", type=int, default=1, help="企业实体ID") parser.add_argument("--period", default=datetime.now().strftime("%Y-%m"), help="期间 YYYY-MM") parser.add_argument("--mpm", type=json.loads, help="MPM结果JSON (内联)") parser.add_argument("--mpm-file", help="MPM结果JSON文件路径") parser.add_argument("--kpi-code", help="单KPI回填: KPI编码") parser.add_argument("--value", type=float, help="单KPI回填: 数值") parser.add_argument("--source", default="finance-bot", help="来源Bot标识") parser.add_argument("--ping", action="store_true", help="连通性测试") args = parser.parse_args() if args.ping: try: resp = httpx.get(f"{CMA_API}/../bot/ping", headers=_HEADERS, timeout=10) print(f"✅ bot-bridge API连通 (HTTP {resp.status_code})") return 0 except Exception as e: print(f"❌ 连接失败: {e}") return 1 if args.mpm_file: with open(args.mpm_file, encoding="utf-8") as f: mpm = json.load(f) else: mpm = args.mpm if mpm: summary = push_mpm_results(args.entity_id, args.period, mpm, args.source) print(json.dumps(summary, ensure_ascii=False, indent=2)) return 0 if summary["kpi_failed"] == 0 else 1 if args.kpi_code: if args.value is None: print("❌ 单KPI回填需要 --value") return 1 result = push_kpi_result(args.entity_id, args.period, args.kpi_code, args.value, args.source) print(json.dumps(result, ensure_ascii=False, indent=2)) return 0 if result["success"] else 1 parser.print_help() return 1 if __name__ == "__main__": sys.exit(main())