Files
cma-management/backend/scripts/bot_bridge_push.py
T
wecom-fullstackandHermes CI Fix a9dcbf22bc chore(security): 摘除 knowledge_service 硬编码默认 DeepSeek Key(缺 env 抛明确错误/503)
- knowledge_service: 移除 os.getenv 默认值占位,改 _require_deepseek_key() 必填,缺 env 抛 DeepSeekKeyMissing
- knowledge API: generate/{daily,weekly,monthly} 缺 Key 返回 503(可观测,不再静默走模板兜底)
- finance_bot / verify_zxt_vouchers_json / bot_bridge_push: 凭据改 env 优先(CMA_BOT_KEY_* / CMA_BOT_API_KEYS / CMA_BRIDGE_TOKEN),历史共享键仅作兼容期兜底并告警
- tests: 新增 5 例回归(缺 Key 服务层抛错、API 503;有 Key 正常 200)

(cherry picked from commit 859bd8276d7c5aad4bf10ee3399b6cccc188f064)
2026-10-18 16:31:52 +08:00

146 lines
5.4 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.
"""
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")
# 凭据只应从环境变量取;兼容期兜底历史共享 Token(P3 待轮换,轮换后删除兜底)
_LEGACY_BRIDGE_TOKEN = "cma-bot-bridge-2026"
BRIDGE_TOKEN = (os.getenv("CMA_BRIDGE_TOKEN") or "").strip()
if not BRIDGE_TOKEN:
print("[WARN] CMA_BRIDGE_TOKEN 未配置,回退历史共享 Token(待轮换)", file=sys.stderr)
BRIDGE_TOKEN = _LEGACY_BRIDGE_TOKEN
# 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())