diff --git a/backend/app/api/predict.py b/backend/app/api/predict.py index 06789ff4..33bd3a53 100644 --- a/backend/app/api/predict.py +++ b/backend/app/api/predict.py @@ -331,3 +331,275 @@ def api_real_option(data: dict): 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 = 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)}") diff --git a/frontend/components.d.ts b/frontend/components.d.ts index e0ea7ab8..c9b8b727 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -62,6 +62,7 @@ declare module 'vue' { ElTree: typeof import('element-plus/es')['ElTree'] ElUpload: typeof import('element-plus/es')['ElUpload'] GaugeChart: typeof import('./src/components/charts/GaugeChart.vue')['default'] + GrowthQuality: typeof import('./src/components/GrowthQuality.vue')['default'] KnowledgePanel: typeof import('./src/components/KnowledgePanel.vue')['default'] KPIListView: typeof import('./src/components/KPIListView.vue')['default'] MapCanvasDialogs: typeof import('./src/components/map-canvas/MapCanvasDialogs.vue')['default'] diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 44e42646..0dafd47b 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -164,6 +164,7 @@ export const predictApi = { investment: (data: any) => api.post('/predict/investment', data), sensitivity: (data: any) => api.post('/predict/sensitivity', data), scenario: (data: any) => api.post('/predict/scenario', data), + growthQuality: (data: any) => api.post('/predict/growth-quality', data), } export const deviationPushApi = { diff --git a/frontend/src/components/GrowthQuality.vue b/frontend/src/components/GrowthQuality.vue new file mode 100644 index 00000000..6c2b288c --- /dev/null +++ b/frontend/src/components/GrowthQuality.vue @@ -0,0 +1,475 @@ + + + + + diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue index 3a5fcae9..558df88b 100644 --- a/frontend/src/views/Dashboard.vue +++ b/frontend/src/views/Dashboard.vue @@ -124,6 +124,9 @@ + + + @@ -240,6 +243,7 @@ import { ElMessage } from 'element-plus' import { Loading, ArrowRight } from '@element-plus/icons-vue' import { dashboardApi, alertApi } from '../api/index' import axios from 'axios' +import GrowthQuality from '../components/GrowthQuality.vue' const router = useRouter()