149 lines
4.8 KiB
Python
149 lines
4.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
六维风险评分器
|
|
整合所有数据源和分析结果,生成结构化六维风险画像
|
|
"""
|
|
import logging
|
|
from typing import Optional
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# 风险等级映射
|
|
RISK_LEVELS = {
|
|
(0, 30): {"level": "低", "color": "#4CAF50", "emoji": "🟢"},
|
|
(30, 50): {"level": "中低", "color": "#8BC34A", "emoji": "🟡"},
|
|
(50, 70): {"level": "中高", "color": "#FF9800", "emoji": "🟠"},
|
|
(70, 90): {"level": "高", "color": "#F44336", "emoji": "🔴"},
|
|
(90, 101): {"level": "极高", "color": "#B71C1C", "emoji": "⛔"},
|
|
}
|
|
|
|
|
|
def get_risk_level(score: int) -> dict:
|
|
"""根据分数获取风险等级详情"""
|
|
for (low, high), info in RISK_LEVELS.items():
|
|
if low <= score < high:
|
|
return info
|
|
return {"level": "未知", "color": "#9E9E9E", "emoji": "❓"}
|
|
|
|
|
|
def calculate_six_dimension_scores(company_data: dict) -> dict:
|
|
"""
|
|
基于企业数据直接计算六维风险评分(不依赖 LLM)
|
|
用于快速预览和规则引擎降级场景
|
|
"""
|
|
financials = company_data.get("financials", {})
|
|
compliance = company_data.get("compliance", {})
|
|
tech_route = company_data.get("tech_route", {})
|
|
personnel = company_data.get("core_tech_personnel", [])
|
|
supply_chain = company_data.get("supply_chain", {})
|
|
|
|
scores = {}
|
|
|
|
# 1. 技术路线颠覆风险
|
|
competing = len(tech_route.get("competing_techs", []))
|
|
moat = tech_route.get("tech_moat", "")
|
|
tech_score = min(25 + competing * 15, 85)
|
|
if "差距" in moat or "受制" in moat or "威胁" in moat:
|
|
tech_score = min(tech_score + 15, 95)
|
|
if "领先" in moat or "第一" in moat:
|
|
tech_score = max(tech_score - 10, 10)
|
|
scores["tech_disruption"] = tech_score
|
|
|
|
# 2. 核心人员流失风险
|
|
departed = [p for p in personnel if "离职" in p.get("status", "")]
|
|
high_imp = [p for p in personnel if p.get("importance") == "极高"]
|
|
if departed:
|
|
scores["talent_loss"] = 80
|
|
elif len(high_imp) <= 1 and len(personnel) <= 2:
|
|
scores["talent_loss"] = 55
|
|
else:
|
|
scores["talent_loss"] = 25
|
|
|
|
# 3. 算法/数据合规风险
|
|
algo_status = compliance.get("algo_filing_status", "")
|
|
data_risk = compliance.get("data_export_risk", "低")
|
|
algo_score = 20
|
|
if "未" in algo_status:
|
|
algo_score = 75
|
|
elif data_risk == "高":
|
|
algo_score = 65
|
|
elif data_risk == "中":
|
|
algo_score = 40
|
|
elif "已备案" in algo_status:
|
|
algo_score = 15
|
|
scores["algo_compliance"] = algo_score
|
|
|
|
# 4. 地缘政治/出口管制风险
|
|
entity_status = compliance.get("entity_list_status", "")
|
|
if "被列入" in entity_status:
|
|
scores["geopolitical"] = 92
|
|
elif supply_chain.get("supplier_concentration_risk") == "极高":
|
|
scores["geopolitical"] = 68
|
|
elif supply_chain.get("supplier_concentration_risk") == "高":
|
|
scores["geopolitical"] = 50
|
|
else:
|
|
scores["geopolitical"] = 18
|
|
|
|
# 5. 研发资本化操纵风险
|
|
cap_rate = financials.get("rd_capitalization_rate", 0)
|
|
if cap_rate >= 0.4:
|
|
scores["rd_capitalization"] = 90
|
|
elif cap_rate >= 0.3:
|
|
scores["rd_capitalization"] = 72
|
|
elif cap_rate >= 0.15:
|
|
scores["rd_capitalization"] = 48
|
|
elif cap_rate > 0:
|
|
scores["rd_capitalization"] = 25
|
|
else:
|
|
scores["rd_capitalization"] = 10
|
|
|
|
# 6. 客户/供应商集中风险
|
|
cust_ratio = financials.get("top5_customer_ratio", 0)
|
|
supp_ratio = financials.get("top5_supplier_ratio", 0)
|
|
max_conc = max(cust_ratio, supp_ratio)
|
|
if max_conc >= 0.8:
|
|
scores["concentration"] = 88
|
|
elif max_conc >= 0.6:
|
|
scores["concentration"] = 68
|
|
elif max_conc >= 0.4:
|
|
scores["concentration"] = 42
|
|
else:
|
|
scores["concentration"] = 18
|
|
|
|
# 加权综合
|
|
weights = {
|
|
"tech_disruption": 0.20,
|
|
"talent_loss": 0.15,
|
|
"algo_compliance": 0.15,
|
|
"geopolitical": 0.20,
|
|
"rd_capitalization": 0.15,
|
|
"concentration": 0.15,
|
|
}
|
|
comprehensive = int(sum(scores[k] * weights[k] for k in scores))
|
|
|
|
# 维度中文名映射
|
|
dim_names = {
|
|
"tech_disruption": "技术路线颠覆",
|
|
"talent_loss": "核心人员流失",
|
|
"algo_compliance": "算法/数据合规",
|
|
"geopolitical": "地缘政治/出口管制",
|
|
"rd_capitalization": "研发资本化操纵",
|
|
"concentration": "客户/供应商集中",
|
|
}
|
|
|
|
return {
|
|
"scores": scores,
|
|
"comprehensive_score": comprehensive,
|
|
"risk_level": get_risk_level(comprehensive),
|
|
"dimension_details": {
|
|
k: {
|
|
"name": dim_names[k],
|
|
"score": v,
|
|
"level": get_risk_level(v),
|
|
"weight": weights[k],
|
|
}
|
|
for k, v in scores.items()
|
|
},
|
|
}
|