Files
XH-202626/risk_engine/dynamic_pricing.py
T

185 lines
5.9 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.
# -*- 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