7ec4fb0747
- LLM 客户端:全部 SSE 流式输出,兼容 OpenAI 接口
- AI 月报解析:SSE 流式端点 POST /reports/{id}/parse
- 健康度计算引擎:四维评分(财务/经营/AI商业化/AI成本)
- 风险自动检测引擎:6 条规则自动检测指标越界
- AI Copilot:SSE 流式对话 POST /copilot/chat
- 权限中间件:角色级 + 字段级权限控制
- 测试:21 个新测试(健康度 8 + 风险检测 8 + 权限 5),总计 64 passed
229 lines
6.0 KiB
Python
229 lines
6.0 KiB
Python
"""健康度评分计算引擎。
|
||
|
||
基于月报结构化数据,计算四维评分:
|
||
- 财务健康度(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"
|