feat(backend): AI 服务层 — 千问流式 LLM + 月报解析 + 健康度计算 + 风险检测 + Copilot

- LLM 客户端:全部 SSE 流式输出,兼容 OpenAI 接口
- AI 月报解析:SSE 流式端点 POST /reports/{id}/parse
- 健康度计算引擎:四维评分(财务/经营/AI商业化/AI成本)
- 风险自动检测引擎:6 条规则自动检测指标越界
- AI Copilot:SSE 流式对话 POST /copilot/chat
- 权限中间件:角色级 + 字段级权限控制
- 测试:21 个新测试(健康度 8 + 风险检测 8 + 权限 5),总计 64 passed
This commit is contained in:
selfrelease
2026-07-18 22:16:40 +08:00
parent 94be6189e9
commit 7ec4fb0747
15 changed files with 1244 additions and 52 deletions
+228
View File
@@ -0,0 +1,228 @@
"""健康度评分计算引擎。
基于月报结构化数据,计算四维评分:
- 财务健康度(financial_score
- 经营健康度(operational_score
- AI 商业化度(ai_commercial_score
- AI 成本效率(ai_cost_score
总分 = 加权平均,输出 0-100 分。
"""
import logging
from typing import Any
logger = logging.getLogger(__name__)
# 权重配置
WEIGHTS = {
"financial": 0.35,
"operational": 0.25,
"ai_commercial": 0.25,
"ai_cost": 0.15,
}
def _safe_float(value: Any, default: float = 0.0) -> float:
"""安全转换为 float。"""
if value is None or value == "":
return default
try:
return float(value)
except (ValueError, TypeError):
return default
def _calc_financial_score(data: dict[str, Any]) -> float:
"""计算财务健康度。
指标:
- 现金跑道(runway_months):>12 月=90+6-12=60-90<6=<60
- 营收同比增长(yoy_change):正=加分,负=减分
- 烧钱率趋势(burn_rate trend):down=加分,up=减分
"""
score = 50.0 # 基础分
cash = data.get("cash_balance", {})
runway = _safe_float(cash.get("runway_months"))
if runway > 0:
if runway >= 12:
score += 30
elif runway >= 6:
score += 15
elif runway >= 3:
score -= 10
else:
score -= 30
revenue = data.get("revenue", {})
yoy = revenue.get("yoy_change", "")
if yoy:
yoy_val = _safe_float(str(yoy).replace("%", "").replace("+", ""))
if yoy_val > 0:
score += 15
elif yoy_val < 0:
score -= 15
burn = data.get("burn_rate", {})
trend = burn.get("trend", "")
if trend == "down":
score += 10
elif trend == "up":
score -= 10
return max(0, min(100, score))
def _calc_operational_score(data: dict[str, Any]) -> float:
"""计算经营健康度。
指标:
- 团队规模变化(headcount):净增长=加分
- 关键指标达成情况
"""
score = 60.0
headcount = data.get("headcount", {})
new_hires = _safe_float(headcount.get("new_hires"))
departures = _safe_float(headcount.get("departures"))
net_change = new_hires - departures
if net_change > 0:
score += 15
elif net_change < 0:
score -= 10
if departures > 5:
score -= 10 # 高流失率
key_metrics = data.get("key_metrics", [])
if key_metrics:
positive_count = sum(
1 for m in key_metrics
if _safe_float(str(m.get("change", "")).replace("%", "").replace("+", "")) > 0
)
score += (positive_count / len(key_metrics)) * 20
return max(0, min(100, score))
def _calc_ai_commercial_score(data: dict[str, Any]) -> float:
"""计算 AI 商业化度。
基于 key_metrics 中 AI 相关指标的达成情况。
"""
score = 50.0
key_metrics = data.get("key_metrics", [])
ai_metrics = [
m for m in key_metrics
if "ai" in str(m.get("name", "")).lower()
or "模型" in str(m.get("name", ""))
or "推理" in str(m.get("name", ""))
]
if ai_metrics:
for m in ai_metrics:
change = str(m.get("change", ""))
val = _safe_float(change.replace("%", "").replace("+", ""))
if val > 0:
score += 15
elif val < 0:
score -= 10
else:
# 无 AI 相关指标,给中等偏下分数
score = 40.0
return max(0, min(100, score))
def _calc_ai_cost_score(data: dict[str, Any]) -> float:
"""计算 AI 成本效率。
基于 burn_rate 和 AI 相关支出估算。
"""
score = 55.0
burn = data.get("burn_rate", {})
trend = burn.get("trend", "")
if trend == "down":
score += 20
elif trend == "up":
score -= 15
# 如果有 key_metrics 中的成本相关指标
key_metrics = data.get("key_metrics", [])
cost_metrics = [
m for m in key_metrics
if "成本" in str(m.get("name", "")) or "cost" in str(m.get("name", "")).lower()
]
for m in cost_metrics:
change = str(m.get("change", ""))
val = _safe_float(change.replace("%", "").replace("+", ""))
if val < 0: # 成本下降是好事
score += 10
elif val > 0:
score -= 10
return max(0, min(100, score))
def calculate_health_score(structured_data: dict[str, Any]) -> dict[str, float]:
"""计算四维健康度评分。
Args:
structured_data: 月报 AI 解析后的结构化数据
Returns:
包含 total_score 和四个维度分数的字典
"""
if not structured_data:
return {
"total_score": 0.0,
"financial_score": 0.0,
"operational_score": 0.0,
"ai_commercial_score": 0.0,
"ai_cost_score": 0.0,
}
financial = _calc_financial_score(structured_data)
operational = _calc_operational_score(structured_data)
ai_commercial = _calc_ai_commercial_score(structured_data)
ai_cost = _calc_ai_cost_score(structured_data)
total = (
financial * WEIGHTS["financial"]
+ operational * WEIGHTS["operational"]
+ ai_commercial * WEIGHTS["ai_commercial"]
+ ai_cost * WEIGHTS["ai_cost"]
)
result = {
"total_score": round(total, 1),
"financial_score": round(financial, 1),
"operational_score": round(operational, 1),
"ai_commercial_score": round(ai_commercial, 1),
"ai_cost_score": round(ai_cost, 1),
}
logger.info("健康度评分计算完成: %s", result)
return result
def determine_trend(current_score: float, previous_score: float | None) -> str:
"""判断评分趋势。
Args:
current_score: 当前评分
previous_score: 上期评分(如有)
Returns:
"up" / "down" / "stable"
"""
if previous_score is None:
return "stable"
diff = current_score - previous_score
if diff > 5:
return "up"
elif diff < -5:
return "down"
return "stable"