feat: 初始提交 - 科创企业特有风险的识别与管理 (数智风控系统)

This commit is contained in:
Chen Xiao
2026-08-14 08:21:26 +08:00
commit a41576a8c0
190 changed files with 28531 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
"""风险评估引擎模块"""
+184
View File
@@ -0,0 +1,184 @@
# -*- coding: utf-8 -*-
"""
动态保险定价模型
基于精算原理,结合六维风险评分实现"千企千面"费率计算
"""
import logging
import sys
import os
# 将项目根目录添加到 Python 路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from config import INSURANCE_PRODUCTS
logger = logging.getLogger(__name__)
# 行业风险系数
INDUSTRY_RISK_FACTORS = {
"芯片": 1.35, # 地缘风险+技术迭代双高
"AI": 1.30, # 合规风险+技术竞争
"软件": 1.05, # 相对成熟
"医疗器械": 0.95, # 技术壁垒高但风险较稳
"新能源": 1.15, # 技术路线之争+贸易摩擦
"消费电子": 1.10, # 供应链风险
}
# 企业规模折扣(大企业风险分散能力更强)
SCALE_DISCOUNT = {
"超大型": 0.85, # 营收 > 500亿
"大型": 0.90, # 100-500亿
"中型": 1.00, # 10-100亿
"小型": 1.15, # 1-10亿
"微型": 1.30, # < 1亿
}
def get_enterprise_scale(revenue: float) -> str:
"""根据营收判断企业规模"""
if revenue >= 50_000_000_000:
return "超大型"
elif revenue >= 10_000_000_000:
return "大型"
elif revenue >= 1_000_000_000:
return "中型"
elif revenue >= 100_000_000:
return "小型"
return "微型"
def calculate_premium(
product_key: str,
comprehensive_risk_score: int,
six_dimension_scores: dict,
sector: str = "",
revenue: float = 0,
) -> dict:
"""
动态费率计算器
定价公式:
实际保费 = 基础保费 × 风险系数 × 行业调整 × 规模折扣
风险系数由综合评分决定(分段线性):
- [0, 30) → 0.7 ~ 0.9(优质折扣)
- [30, 50) → 0.9 ~ 1.2(标准浮动)
- [50, 70) → 1.2 ~ 1.8(风险上浮)
- [70, 90) → 1.8 ~ 2.5(惩罚性费率)
- [90,100] → 拒保或附加极高免赔额
"""
product = INSURANCE_PRODUCTS.get(product_key)
if not product:
return {"error": f"未知保险产品: {product_key}"}
base_premium = product["base_premium"]
base_coverage = product["base_coverage"]
# 1. 计算风险系数
risk_multiplier = _calculate_risk_multiplier(comprehensive_risk_score)
# 2. 行业调整系数
industry_factor = INDUSTRY_RISK_FACTORS.get(sector, 1.0)
# 3. 规模折扣
scale = get_enterprise_scale(revenue)
scale_factor = SCALE_DISCOUNT[scale]
# 4. 特定险种的维度调整
dimension_adjustment = _get_dimension_adjustment(product_key, six_dimension_scores)
# 5. 最终保费
final_premium = base_premium * risk_multiplier * industry_factor * scale_factor * dimension_adjustment
# 6. 核保条件
deductible_rate = _calculate_deductible(comprehensive_risk_score)
adjusted_coverage = base_coverage * (1.0 if comprehensive_risk_score < 70 else 0.7)
return {
"product_name": product["name"],
"product_key": product_key,
"base_premium": base_premium,
"base_coverage": base_coverage,
"risk_multiplier": round(risk_multiplier, 3),
"industry_factor": round(industry_factor, 3),
"scale_factor": round(scale_factor, 3),
"scale_label": scale,
"dimension_adjustment": round(dimension_adjustment, 3),
"final_premium": round(final_premium, 2),
"adjusted_coverage": round(adjusted_coverage, 2),
"deductible_rate": round(deductible_rate, 3),
"deductible_amount": round(adjusted_coverage * deductible_rate, 2),
"comprehensive_risk_score": comprehensive_risk_score,
"is_insurable": comprehensive_risk_score < 90,
"pricing_breakdown": (
f"¥{base_premium:,.0f} × {risk_multiplier:.2f}(风险) "
f"× {industry_factor:.2f}(行业) × {scale_factor:.2f}(规模) "
f"× {dimension_adjustment:.2f}(维度) = ¥{final_premium:,.0f}"
),
}
def _calculate_risk_multiplier(score: int) -> float:
"""分段线性风险系数计算"""
if score < 30:
return 0.7 + (score / 30) * 0.2 # 0.7 ~ 0.9
elif score < 50:
return 0.9 + ((score - 30) / 20) * 0.3 # 0.9 ~ 1.2
elif score < 70:
return 1.2 + ((score - 50) / 20) * 0.6 # 1.2 ~ 1.8
elif score < 90:
return 1.8 + ((score - 70) / 20) * 0.7 # 1.8 ~ 2.5
else:
return 3.0 # 拒保级别
def _get_dimension_adjustment(product_key: str, scores: dict) -> float:
"""针对特定险种,根据相关维度得分进行微调"""
if product_key == "ip_lawsuit":
# 知识产权被诉险:重点看技术路线和专利
tech_score = scores.get("tech_disruption", 50)
return 0.8 + (tech_score / 100) * 0.4 # 0.8 ~ 1.2
elif product_key == "exec_departure":
# 高管离职险:重点看人员流失风险
talent_score = scores.get("talent_loss", 50)
return 0.7 + (talent_score / 100) * 0.6 # 0.7 ~ 1.3
elif product_key == "data_compliance":
# 数据合规险:重点看合规风险
compliance_score = scores.get("algo_compliance", 50)
return 0.8 + (compliance_score / 100) * 0.4 # 0.8 ~ 1.2
return 1.0
def _calculate_deductible(score: int) -> float:
"""计算免赔率"""
if score < 30:
return 0.05 # 5%
elif score < 50:
return 0.10 # 10%
elif score < 70:
return 0.15 # 15%
elif score < 90:
return 0.25 # 25%
else:
return 0.50 # 50%(惩罚性高免赔)
def calculate_all_products(
comprehensive_risk_score: int,
six_dimension_scores: dict,
sector: str = "",
revenue: float = 0,
) -> list:
"""计算所有险种的保费"""
results = []
for product_key in INSURANCE_PRODUCTS:
result = calculate_premium(
product_key, comprehensive_risk_score,
six_dimension_scores, sector, revenue,
)
results.append(result)
return results
+146
View File
@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
"""
核保决策报告生成器
生成结构化的风险评估报告
"""
import json
import logging
from datetime import datetime
from typing import Optional
logger = logging.getLogger(__name__)
def generate_report(
company_data: dict,
debate_result: dict,
pricing_results: list,
) -> dict:
"""
生成完整的核保决策报告
"""
company_name = company_data.get("short_name", "未知企业")
judge = debate_result.get("judge_result", {})
scores = judge.get("six_dimension_scores", {})
comprehensive = judge.get("comprehensive_score", 50)
decision = judge.get("underwriting_decision", "标准承保")
report = {
"title": f"科创企业智能风控核保报告 —— {company_name}",
"report_id": f"RPT-{datetime.now().strftime('%Y%m%d%H%M%S')}",
"generate_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"company_profile": {
"name": company_name,
"stock_code": company_data.get("stock_code", ""),
"industry": company_data.get("industry", ""),
"sector": company_data.get("sector", ""),
"description": company_data.get("description", ""),
},
"risk_assessment": {
"comprehensive_score": comprehensive,
"risk_level": judge.get("risk_level", ""),
"six_dimension_scores": scores,
"key_risks": judge.get("key_risks", []),
},
"debate_summary": {
"law_findings": debate_result.get("law_result", {}).get("key_findings", []),
"tech_findings": debate_result.get("tech_result", {}).get("key_findings", []),
"fin_findings": debate_result.get("finance_result", {}).get("key_findings", []),
"conflicts": debate_result.get("conflicts", []),
"resolution": judge.get("conflict_resolution", ""),
},
"underwriting_decision": {
"decision": decision,
"conditions": judge.get("underwriting_conditions", []),
"summary": judge.get("summary", ""),
},
"pricing": pricing_results,
}
return report
def format_report_markdown(report: dict) -> str:
"""将报告格式化为 Markdown"""
lines = []
lines.append(f"# {report['title']}")
lines.append(f"\n> 报告编号: {report['report_id']} | 生成时间: {report['generate_time']}")
# 企业概况
profile = report["company_profile"]
lines.append("\n## 一、企业概况")
lines.append(f"- **企业名称**: {profile['name']}")
lines.append(f"- **股票代码**: {profile['stock_code']}")
lines.append(f"- **所属行业**: {profile['industry']}")
lines.append(f"- **企业描述**: {profile['description']}")
# 风险评估
risk = report["risk_assessment"]
lines.append("\n## 二、风险评估")
lines.append(f"- **综合风险评分**: {risk['comprehensive_score']}")
lines.append(f"- **风险等级**: {risk['risk_level']}")
lines.append("\n### 六维风险评分")
dim_names = {
"tech_disruption": "技术路线颠覆",
"talent_loss": "核心人员流失",
"algo_compliance": "算法/数据合规",
"geopolitical": "地缘政治/出口管制",
"rd_capitalization": "研发资本化操纵",
"concentration": "客户/供应商集中",
}
lines.append("| 风险维度 | 评分 | 等级 |")
lines.append("|---------|------|------|")
for dim, score in risk.get("six_dimension_scores", {}).items():
name = dim_names.get(dim, dim)
level = "" if score >= 70 else ("" if score >= 40 else "")
lines.append(f"| {name} | {score} | {level} |")
# 辩论摘要
debate = report["debate_summary"]
lines.append("\n## 三、多智能体交叉验证摘要")
lines.append("\n### 👩‍⚖️ 法务风控节点")
for f in debate.get("law_findings", []):
lines.append(f"- {f}")
lines.append("\n### 👨‍🔬 技术风控节点")
for f in debate.get("tech_findings", []):
lines.append(f"- {f}")
lines.append("\n### 👔 财务风控节点")
for f in debate.get("fin_findings", []):
lines.append(f"- {f}")
if debate.get("conflicts"):
lines.append("\n### ⚠️ 冲突点")
for c in debate["conflicts"]:
lines.append(f"- {c}")
# 核保决策
uw = report["underwriting_decision"]
lines.append(f"\n## 四、核保决策")
lines.append(f"\n**决策**: 【{uw['decision']}")
lines.append(f"\n{uw.get('summary', '')}")
if uw.get("conditions"):
lines.append("\n### 核保附加条件")
for c in uw["conditions"]:
lines.append(f"- {c}")
# 保费方案
lines.append("\n## 五、保险产品费率方案")
if report.get("pricing"):
lines.append("| 险种 | 基础保费 | 最终保费 | 保额 | 免赔率 |")
lines.append("|------|---------|---------|------|--------|")
for p in report["pricing"]:
if p.get("is_insurable"):
lines.append(
f"| {p['product_name']} | ¥{p['base_premium']:,.0f} | "
f"¥{p['final_premium']:,.0f} | ¥{p['adjusted_coverage']:,.0f} | "
f"{p['deductible_rate']:.0%} |"
)
else:
lines.append(f"| {p['product_name']} | - | **拒保** | - | - |")
return "\n".join(lines)
+148
View File
@@ -0,0 +1,148 @@
# -*- 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()
},
}