Files
cma-management/backend/app/api/predict.py
T

611 lines
28 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.
"""预测模拟API — 管理会计OS"""
import logging
from fastapi import APIRouter, HTTPException
from app.utils.predict_engine import (
cvp_analysis, npv, irr,
sensitivity_analysis, scenario_analysis,
)
logger = logging.getLogger("cma.predict")
router = APIRouter(prefix="/api/cma/predict", tags=["预测模拟"])
@router.post("/cvp")
def api_cvp_analysis(data: dict):
"""CVP本量利分析"""
try:
result = cvp_analysis(
unit_price=float(data.get("unit_price", 0)),
unit_variable_cost=float(data.get("unit_variable_cost", 0)),
fixed_cost=float(data.get("fixed_cost", 0)),
target_profit=float(data["target_profit"]) if data.get("target_profit") else None,
actual_volume=float(data["actual_volume"]) if data.get("actual_volume") else None,
)
return result
except Exception as e:
raise HTTPException(400, f"CVP计算失败: {str(e)}")
@router.post("/investment")
def api_investment_analysis(data: dict):
"""投资决策分析(NPV/IRR/回收期)"""
try:
initial = float(data.get("initial_investment", 0))
rate = float(data.get("discount_rate", 10))
cash_flows = [float(cf) for cf in data.get("cash_flows", [])]
if not cash_flows:
raise HTTPException(400, "现金流列表不能为空")
npv_result = npv(initial, cash_flows, rate)
irr_result = irr(initial, cash_flows)
return {
"npv_analysis": npv_result,
"irr_analysis": irr_result,
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(400, f"投资决策计算失败: {str(e)}")
@router.post("/sensitivity")
def api_sensitivity_analysis(data: dict):
"""敏感性分析"""
try:
result = sensitivity_analysis(
base_revenue=float(data.get("base_revenue", 0)),
base_cost=float(data.get("base_cost", 0)),
base_profit=float(data["base_profit"]) if data.get("base_profit") else None,
step=int(data.get("step", 5)),
max_step=int(data.get("max_step", 20)),
)
return result
except Exception as e:
raise HTTPException(400, f"敏感性分析失败: {str(e)}")
@router.post("/scenario")
def api_scenario_analysis(data: dict):
"""情景模拟"""
try:
optimistic = data.get("optimistic", {})
pessimistic = data.get("pessimistic", {})
base = data.get("base", {})
if not all([optimistic, pessimistic, base]):
raise HTTPException(400, "需要提供乐观/中性/悲观三个情景的参数")
result = scenario_analysis(
optimistic={
"revenue": float(optimistic.get("revenue", 0)),
"cost": float(optimistic.get("cost", 0)),
},
pessimistic={
"revenue": float(pessimistic.get("revenue", 0)),
"cost": float(pessimistic.get("cost", 0)),
},
base={
"revenue": float(base.get("revenue", 0)),
"cost": float(base.get("cost", 0)),
},
)
return result
except HTTPException:
raise
except Exception as e:
raise HTTPException(400, f"情景模拟失败: {str(e)}")
@router.post("/cvp-detailed")
def api_cvp_detailed(data: dict):
"""CVP本量利详细分析 — 含改善方案推演和保本图数据 (CMA P2)"""
try:
fixed_cost = float(data.get("fixed_cost", 617))
variable_cost_rate = float(data.get("variable_cost_rate", 0.4862))
unit_price = float(data.get("unit_price", 228))
current_volume = float(data.get("current_volume", 5300))
contribution_margin_rate = 1 - variable_cost_rate
breakeven_revenue = round(fixed_cost / contribution_margin_rate, 2)
breakeven_units = round(breakeven_revenue * 10000 / unit_price, 0)
current_revenue = round(current_volume * unit_price / 10000, 2)
current_profit = round(current_revenue * (1 - variable_cost_rate) - fixed_cost, 2)
safety_margin = round((current_revenue - breakeven_revenue) / current_revenue * 100, 2) if current_revenue > 0 else 0
scenarios = [
{"name": "降固定费用至300万", "fixed_cost": 300, "variable_cost_rate": variable_cost_rate,
"breakeven_revenue": round(300 / contribution_margin_rate, 2),
"breakeven_units": round(300 / contribution_margin_rate * 10000 / unit_price, 0)},
{"name": "降变动成本率至30%", "fixed_cost": fixed_cost, "variable_cost_rate": 0.3,
"breakeven_revenue": round(fixed_cost / 0.7, 2),
"breakeven_units": round(fixed_cost / 0.7 * 10000 / unit_price, 0)},
{"name": "两者同时改善", "fixed_cost": 300, "variable_cost_rate": 0.3,
"breakeven_revenue": round(300 / 0.7, 2),
"breakeven_units": round(300 / 0.7 * 10000 / unit_price, 0)},
]
# 保本图数据点
chart_data = []
max_volume = int(max(breakeven_units * 2, current_volume * 3))
step = max(1, int(max_volume / 20))
for vol in range(0, int(max_volume) + step, step):
rev = round(vol * unit_price / 10000, 2)
tc = round(fixed_cost + rev * variable_cost_rate, 2)
chart_data.append({"volume": vol, "revenue": rev, "total_cost": tc, "profit": round(rev - tc, 2)})
return {
"fixed_cost": fixed_cost,
"variable_cost_rate": round(variable_cost_rate * 100, 2),
"unit_price": unit_price,
"contribution_margin_rate": round(contribution_margin_rate * 100, 2),
"breakeven_revenue": breakeven_revenue,
"breakeven_units": int(breakeven_units),
"current_revenue": current_revenue,
"current_profit": current_profit,
"current_volume": int(current_volume),
"safety_margin": safety_margin,
"scenarios": scenarios,
"chart_data": chart_data,
}
except Exception as e:
raise HTTPException(400, f"CVP详细分析失败: {str(e)}")
# ── 实物期权计算器 ─────────────────────────────────────────────
import math
def _norm_cdf(x: float) -> float:
"""标准正态分布CDF — Abramowitz & Stegun 近似 (max error ≈ 1.5×10⁻⁷)"""
a1, a2, a3, a4, a5 = 0.254829592, -0.284496736, 1.421413741, -1.453152027, 1.061405429
p = 0.3275911
sign = 1.0
if x < 0:
sign = -1.0
x_abs = abs(x) / math.sqrt(2.0)
t = 1.0 / (1.0 + p * x_abs)
y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * math.exp(-x_abs * x_abs)
return 0.5 * (1.0 + sign * y)
def _black_scholes_call(S0: float, X: float, t: float, r: float, sigma: float) -> dict:
"""BSM看涨期权定价(扩张期权/延迟期权)"""
sqrt_t = math.sqrt(t)
d1 = (math.log(S0 / X) + (r + 0.5 * sigma ** 2) * t) / (sigma * sqrt_t)
d2 = d1 - sigma * sqrt_t
nd1 = _norm_cdf(d1)
nd2 = _norm_cdf(d2)
call_value = max(S0 * nd1 - X * math.exp(-r * t) * nd2, 0.0)
return {"value": round(call_value, 4), "d1": round(d1, 4), "d2": round(d2, 4), "Nd1": round(nd1, 4), "Nd2": round(nd2, 4)}
def _black_scholes_put(S0: float, X: float, t: float, r: float, sigma: float) -> dict:
"""BSM看跌期权定价(放弃期权/收缩期权)"""
sqrt_t = math.sqrt(t)
d1 = (math.log(S0 / X) + (r + 0.5 * sigma ** 2) * t) / (sigma * sqrt_t)
d2 = d1 - sigma * sqrt_t
nd1 = _norm_cdf(-d1)
nd2 = _norm_cdf(-d2)
put_value = max(X * math.exp(-r * t) * nd2 - S0 * nd1, 0.0)
return {"value": round(put_value, 4), "d1": round(d1, 4), "d2": round(d2, 4), "N(-d1)": round(nd1, 4), "N(-d2)": round(nd2, 4)}
def _binomial_tree_call(S0: float, X: float, t: float, r: float, sigma: float, n: int = 100) -> float:
"""二叉树欧式看涨期权定价(延迟期权)"""
dt = t / n
u = math.exp(sigma * math.sqrt(dt))
d = 1.0 / u
p = (math.exp(r * dt) - d) / (u - d)
discount = math.exp(-r * dt)
prices = [S0 * (u ** (n - j)) * (d ** j) for j in range(n + 1)]
values = [max(p - X, 0.0) for p in prices]
for i in range(n - 1, -1, -1):
for j in range(i + 1):
values[j] = discount * (p * values[j] + (1 - p) * values[j + 1])
return max(values[0], 0.0)
def _binomial_tree_american_put(S0: float, X: float, t: float, r: float, sigma: float, n: int = 100) -> float:
"""二叉树美式看跌期权定价(可随时放弃的放弃期权)"""
dt = t / n
u = math.exp(sigma * math.sqrt(dt))
d = 1.0 / u
p = (math.exp(r * dt) - d) / (u - d)
discount = math.exp(-r * dt)
prices = [S0 * (u ** (n - j)) * (d ** j) for j in range(n + 1)]
values = [max(X - p, 0.0) for p in prices]
for i in range(n - 1, -1, -1):
for j in range(i + 1):
hold = discount * (p * values[j] + (1 - p) * values[j + 1])
exercise = X - (S0 * (u ** (i - j)) * (d ** j))
values[j] = max(hold, exercise)
return max(values[0], 0.0)
@router.post("/real-option")
def api_real_option(data: dict):
"""实物期权计算器"""
try:
opt_type = data.get("opt_type", "expansion") # expansion|abandon|delay|shrink
model = data.get("model", "bs") # bs|binomial
S0 = float(data.get("S0", 100.0))
X = float(data.get("X", 80.0))
t = float(data.get("t", 3.0))
r = float(data.get("r", 0.0174))
sigma = float(data.get("sigma", 0.30))
expansion_factor = float(data.get("expansion_factor", 1.5))
salvage_value = float(data.get("salvage_value", S0 * 0.3))
n_steps = int(data.get("n_steps", 100))
# 输入校验
if S0 <= 0 or X <= 0 or t <= 0 or sigma <= 0:
raise HTTPException(400, "参数必须为正数")
if sigma > 2.0:
raise HTTPException(400, "波动率σ不能超过200%")
result = {"option_type": opt_type, "model": model, "S0": S0, "X": X, "t": t, "r": r, "sigma": sigma}
# 计算期权价值
if opt_type in ("expansion", "delay") and model == "bs":
bs = _black_scholes_call(S0, X, t, r, sigma)
result["option_value"] = bs["value"]
result["intermediate"] = {k: v for k, v in bs.items() if k != "value"}
elif opt_type == "expansion" and model == "binomial":
adj_X = X / expansion_factor
bt_val = _binomial_tree_call(S0, adj_X, t, r, sigma, n_steps)
option_value = max(bt_val * expansion_factor, 0.0)
result["option_value"] = round(option_value, 4)
result["intermediate"] = {"expansion_factor": expansion_factor, "adjusted_X": round(adj_X, 4), "tree_value": round(bt_val, 4)}
elif opt_type == "delay" and model == "binomial":
option_value = _binomial_tree_call(S0, X, t, r, sigma, n_steps)
result["option_value"] = round(option_value, 4)
# Also compute BS for reference
bs = _black_scholes_call(S0, X, t, r, sigma)
result["intermediate"] = {"n_steps": n_steps, "bs_reference": round(bs["value"], 4)}
elif opt_type in ("abandon", "shrink") and model == "bs":
effective_X = salvage_value if opt_type == "abandon" else X
bs = _black_scholes_put(S0, effective_X, t, r, sigma)
result["option_value"] = bs["value"]
result["intermediate"] = {k: v for k, v in bs.items() if k != "value"}
if opt_type == "abandon":
result["intermediate"]["salvage_value"] = effective_X
elif opt_type == "abandon" and model == "binomial":
bt_val = _binomial_tree_american_put(S0, salvage_value, t, r, sigma, n_steps)
result["option_value"] = round(bt_val, 4)
result["intermediate"] = {"n_steps": n_steps, "salvage_value": salvage_value}
else:
raise HTTPException(400, f"不支持的组合: {opt_type} + {model}")
# 决策建议
val = result["option_value"]
if val > 0:
result["suggestion"] = "期权价值 > 0,管理弹性有价值,建议保留决策弹性,在有利时机行权"
result["suggestion_type"] = "positive"
else:
result["suggestion"] = "期权价值 ≈ 0,弹性无明显价值,建议按传统NPV决策,无需等待"
result["suggestion_type"] = "neutral"
# 扩展NPV(假设传统NPV = S0 - X
npv_without = S0 - X
expanded_npv = npv_without + val
result["npv_without_flexibility"] = round(npv_without, 4)
result["expanded_npv"] = round(expanded_npv, 4)
if expanded_npv > 0:
result["decision"] = "✅ 扩展NPV > 0,含弹性后项目整体值得投资"
else:
result["decision"] = "❌ 扩展NPV ≤ 0,含弹性后项目仍不值得投资"
# 敏感性分析数据(σ从10%~90%变化)
sensitivity = []
for s_pct in range(5, 96, 5):
s = s_pct / 100.0
if opt_type in ("expansion", "delay"):
if model == "bs":
v = _black_scholes_call(S0, X, t, r, s)["value"]
else:
bt = _binomial_tree_call(S0, X, t, r, s, n_steps)
v = bt * expansion_factor if opt_type == "expansion" else bt
else:
eff_X = salvage_value if opt_type == "abandon" else X
if model == "bs":
v = _black_scholes_put(S0, eff_X, t, r, s)["value"]
else:
v = _binomial_tree_american_put(S0, eff_X, t, r, s, n_steps)
sensitivity.append({"sigma": s_pct, "option_value": round(v, 4)})
result["sensitivity"] = sensitivity
# 警告提示
warnings = []
if t * sigma * sigma * 0.5 > r:
warnings.append("高波动+长时间,延迟价值显著")
if S0 < X:
warnings.append("价外期权,期权价值较低")
if S0 > X * 1.5:
warnings.append("深度价内,几乎确定行权")
if sigma < 0.10:
warnings.append("波动率过低,期权价值趋近于0")
if t > 10:
warnings.append("长期期权,贴现因子影响大")
result["warnings"] = warnings
return result
except HTTPException:
raise
except Exception as e:
raise HTTPException(400, f"实物期权计算失败: {str(e)}")
# ── 增长质量诊断 ─────────────────────────────────────────────
def _score_revenue_structure(entity: dict) -> int:
"""营收结构评分:渠补率越低越好"""
rebate_rate = float(entity.get("rebateRate", entity.get("rebate_rate", 0)))
if rebate_rate > 80: return 1
if rebate_rate > 60: return 2
if rebate_rate > 40: return 3
if rebate_rate > 20: return 4
return 5
def _score_profit_structure(entity: dict) -> int:
"""利润结构评分:真实毛利率越高越好"""
gross_margin = float(entity.get("trueGrossMargin", entity.get("true_gross_margin", 0)))
if gross_margin < 0: return 1
if gross_margin < 10: return 2
if gross_margin < 20: return 3
if gross_margin < 30: return 4
return 5
def _score_cash_assets(entity: dict) -> int:
"""现金资产评分:现金比率越高越好"""
cash_ratio = float(entity.get("cashRatio", entity.get("cash_ratio", 0)))
if cash_ratio < 10: return 1
if cash_ratio < 30: return 2
if cash_ratio < 50: return 3
if cash_ratio < 100: return 4
return 5
def _score_growth_driver(entity: dict) -> int:
"""增长驱动评分:费用增速相对收入增速越低越好"""
expense_growth = float(entity.get("expenseGrowthRate", entity.get("expense_growth_rate", 0)))
revenue_growth = float(entity.get("revenueGrowthRate", entity.get("revenue_growth_rate", 1)))
if revenue_growth <= 0: revenue_growth = 1 # prevent div by zero
ratio = expense_growth / revenue_growth
if ratio > 1.5: return 1
if ratio > 1.2: return 2
if ratio > 1.0: return 3
if ratio > 0.8: return 4
return 5
def _score_org_efficiency(entity: dict) -> int:
"""组织效率评分:管理费/净收入越低越好"""
mgmt_ratio = float(entity.get("mgmtRatio", entity.get("mgmt_ratio", 0)))
if mgmt_ratio > 300: return 1
if mgmt_ratio > 200: return 2
if mgmt_ratio > 100: return 3
if mgmt_ratio > 50: return 4
return 5
def _diagnosis_text(overall: float, dimensions: dict, entity_name: str) -> str:
"""根据评分生成诊断结论"""
lines = []
low_dims = {k: v for k, v in dimensions.items() if v["score"] <= 2}
mid_dims = {k: v for k, v in dimensions.items() if 2 < v["score"] < 4}
dim_labels = {
"revenueStructure": "营收结构",
"profitStructure": "利润结构",
"cashAssets": "现金资产",
"growthDriver": "增长驱动",
"orgEfficiency": "组织效率",
}
if overall < 2:
lines.append(f"{entity_name}的增长质量评分仅{overall}分,属于「越增长越重」类型。")
lines.append("增长主要依赖资源投入而非核心能力积累,可持续性堪忧。")
elif overall < 3:
lines.append(f"{entity_name}的增长质量评分{overall}分,需重点关注。")
lines.append("部分维度存在风险,增长质量有待改善。")
elif overall < 4:
lines.append(f"{entity_name}的增长质量评分{overall}分,处于中等水平。")
lines.append("多数维度表现尚可,仍有优化空间。")
else:
lines.append(f"{entity_name}的增长质量评分{overall}分,「越增长越轻」。")
lines.append("增长模式健康,具备持续增长能力。")
if low_dims:
low_names = [dim_labels.get(k, k) for k in low_dims]
lines.append(f"⚠️ 需重点关注:{'、'.join(low_names)}评分偏低(≤2分)。")
if mid_dims:
mid_names = [dim_labels.get(k, k) for k in mid_dims]
lines.append(f"💡 可优化:{'、'.join(mid_names)}有提升空间。")
# 具体建议(硬编码的关键诊断)
if dimensions.get("revenueStructure", {}).get("score", 5) <= 2:
lines.append("• 营收依赖渠道返利,建议降低渠补率、拓展直销渠道。")
if dimensions.get("orgEfficiency", {}).get("score", 5) <= 2:
lines.append("• 管理费率高企,建议精简费用结构、优化运营效率。")
if dimensions.get("cashAssets", {}).get("score", 5) <= 2:
lines.append("• 现金比率极低,存在断流风险,建议加强现金流管理。")
if dimensions.get("growthDriver", {}).get("score", 5) <= 2:
lines.append("• 费用增速远超收入增速,增长不可持续,需控制费用膨胀。")
return "\n".join(lines)
def _generate_improvement_suggestions(dimension: str, score: int, entity: dict) -> list:
"""为指定维度生成改善建议"""
suggestions = []
if dimension == "revenueStructure":
rebate = float(entity.get("rebateRate", entity.get("rebate_rate", 0)))
if score <= 2:
target_rebate = max(rebate - 10, 0)
savings = f"释放现金{round(rebate - target_rebate, 1)}%/月"
suggestions.append(f"渠补谈判:{rebate}%→{target_rebate}%{savings}")
suggestions.append("客户分散:拓展直销渠道,降低渠道依赖")
suggestions.append("渠补制度:分级管理,差异化返利")
else:
suggestions.append("维持现有渠补政策")
elif dimension == "profitStructure":
gm = float(entity.get("trueGrossMargin", entity.get("true_gross_margin", 0)))
if score <= 2:
suggestions.append(f"成本优化:毛利率仅{gm}%,需分析成本构成")
suggestions.append("产品结构:提高高毛利产品占比")
suggestions.append("定价策略:评估提价空间")
else:
suggestions.append("维持毛利率水平")
elif dimension == "cashAssets":
cr = float(entity.get("cashRatio", entity.get("cash_ratio", 0)))
if score <= 2:
suggestions.append(f"现金管理:现金比率仅{cr}%,存在断流风险")
suggestions.append("应收账款:加快回款周期")
suggestions.append("融资安排:准备短期授信额度")
else:
suggestions.append("维持现金流健康")
elif dimension == "growthDriver":
eg = float(entity.get("expenseGrowthRate", entity.get("expense_growth_rate", 0)))
if score <= 2:
suggestions.append(f"费用管控:费用增速{eg}倍于收入,需严控费用")
suggestions.append("预算管理:建立费用增长红线机制")
suggestions.append("投资回报:评估每项投入的ROI")
else:
suggestions.append("维持费用增长与收入增长匹配")
elif dimension == "orgEfficiency":
mr = float(entity.get("mgmtRatio", entity.get("mgmt_ratio", 0)))
if score <= 2:
suggestions.append(f"管理效率:管理费/净收入{mr}%,急需降本增效")
suggestions.append("组织精简:评估管理层级压缩空间")
suggestions.append("流程优化:推进数字化降本")
else:
suggestions.append("维持管理效率水平")
return suggestions
def _get_dim_detail_indicators(dimension: str, entity: dict) -> list:
"""获取维度的明细诊断指标"""
indicators = []
if dimension == "revenueStructure":
rebate = float(entity.get("rebateRate", entity.get("rebate_rate", 0)))
net_ratio = round(100 - rebate, 1)
indicators.append({"label": "渠补率", "value": f"{rebate}%",
"verdict": "收入依赖渠道返利" if rebate > 50 else "渠道依赖程度中等",
"status": "danger" if rebate > 50 else "warning" if rebate > 20 else "success"})
indicators.append({"label": "净收入占比", "value": f"{net_ratio}%",
"verdict": f"仅{net_ratio}%归公司" if net_ratio < 30 else "净收入占比合理",
"status": "danger" if net_ratio < 30 else "success"})
elif dimension == "profitStructure":
gm = float(entity.get("trueGrossMargin", entity.get("true_gross_margin", 0)))
indicators.append({"label": "真实毛利率", "value": f"{gm}%",
"verdict": "毛利偏低" if gm < 15 else "毛利正常",
"status": "danger" if gm < 10 else "warning" if gm < 20 else "success"})
elif dimension == "cashAssets":
cr = float(entity.get("cashRatio", entity.get("cash_ratio", 0)))
indicators.append({"label": "现金比率", "value": f"{cr}%",
"verdict": "断流风险" if cr < 5 else "现金紧张" if cr < 30 else "现金充足",
"status": "danger" if cr < 5 else "warning" if cr < 30 else "success"})
elif dimension == "growthDriver":
eg = float(entity.get("expenseGrowthRate", entity.get("expense_growth_rate", 0)))
rg = float(entity.get("revenueGrowthRate", entity.get("revenue_growth_rate", 1)))
ratio = eg / rg if rg > 0 else 99
indicators.append({"label": "费用增速/收入增速", "value": f"{ratio:.1f}倍",
"verdict": "费用增速过快" if ratio > 1.5 else "费用可控" if ratio > 1 else "增长健康",
"status": "danger" if ratio > 1.5 else "warning" if ratio > 1 else "success"})
elif dimension == "orgEfficiency":
mr = float(entity.get("mgmtRatio", entity.get("mgmt_ratio", 0)))
indicators.append({"label": "管理费/净收入", "value": f"{mr}%",
"verdict": "管理费极高" if mr > 200 else "管理费偏高" if mr > 100 else "管理费正常",
"status": "danger" if mr > 200 else "warning" if mr > 100 else "success"})
return indicators
@router.post("/growth-quality")
def api_growth_quality(data: dict):
"""增长质量诊断 — 五维度评分+综合评分+诊断结论"""
try:
entity_id = data.get("entity_id")
ENTITY_DATA = {
1: {"entity":"陕西酣客文化传媒","rebateRate":82.8,"trueGrossMargin":18.6,"cashRatio":0.6,"expenseGrowthRate":2.2,"revenueGrowthRate":1.0,"mgmtRatio":447},
2: {"entity":"陕西博海网络科技","rebateRate":0,"trueGrossMargin":13.1,"cashRatio":6.7,"expenseGrowthRate":0.8,"revenueGrowthRate":1.0,"mgmtRatio":1.4},
}
entity = ENTITY_DATA.get(entity_id, data.get("entity", data))
entity_name = entity.get("entity", entity.get("name", "该企业"))
period = entity.get("period", data.get("period", "当前"))
# 五维度评分
dim_scores = {
"revenueStructure": _score_revenue_structure(entity),
"profitStructure": _score_profit_structure(entity),
"cashAssets": _score_cash_assets(entity),
"growthDriver": _score_growth_driver(entity),
"orgEfficiency": _score_org_efficiency(entity),
}
overall = round(sum(dim_scores.values()) / 5, 1)
# 综合等级
if overall >= 4:
level = "🟢 越增长越轻"
level_type = "excellent"
elif overall >= 3:
level = "🟡 增长质量中等"
level_type = "medium"
elif overall >= 2:
level = "🟠 需关注"
level_type = "warning"
else:
level = "🔴 越增长越重"
level_type = "danger"
# 诊断结论
dimensions_payload = {}
detail_payload = {}
for dim, score in dim_scores.items():
dimensions_payload[dim] = {"score": score, "weight": 20}
detail_payload[dim] = {
"score": score,
"indicators": _get_dim_detail_indicators(dim, entity),
"suggestions": _generate_improvement_suggestions(dim, score, entity),
}
diagnosis = _diagnosis_text(overall, dimensions_payload, entity_name)
# 对比数据(如果请求中包含多个实体)
compare = data.get("compare", None)
compare_result = None
if compare:
compare_entity = compare
compare_name = compare_entity.get("entity", compare_entity.get("name", "对比企业"))
cdims = {
"revenueStructure": _score_revenue_structure(compare_entity),
"profitStructure": _score_profit_structure(compare_entity),
"cashAssets": _score_cash_assets(compare_entity),
"growthDriver": _score_growth_driver(compare_entity),
"orgEfficiency": _score_org_efficiency(compare_entity),
}
compare_overall = round(sum(cdims.values()) / 5, 1)
compare_result = {
"entity_name": compare_name,
"overall": compare_overall,
"dimensions": {k: {"score": v, "weight": 20} for k, v in cdims.items()},
"level": ("🟢 越增长越轻" if compare_overall >= 4 else
"🟡 增长质量中等" if compare_overall >= 3 else
"🟠 需关注" if compare_overall >= 2 else "🔴 越增长越重"),
}
return {
"entity_name": entity_name,
"period": period,
"overall": overall,
"level": level,
"level_type": level_type,
"dimensions": dimensions_payload,
"detail": detail_payload,
"diagnosis": diagnosis,
"compare": compare_result,
}
except Exception as e:
raise HTTPException(400, f"增长质量诊断失败: {str(e)}")