Files
XH-202626/XH-202626_数智风控系统_全国竞赛最终提交材料包/03_原型系统源码/pages/4_💰_动态定价与核保.py
T

284 lines
12 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 sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import streamlit as st
import plotly.graph_objects as go
from collectors.financial_collector import get_all_companies, get_company_by_code
from risk_engine.risk_scorer import calculate_six_dimension_scores
from risk_engine.dynamic_pricing import (
calculate_premium, calculate_all_products,
get_enterprise_scale, INDUSTRY_RISK_FACTORS,
)
from risk_engine.report_generator import generate_report, format_report_markdown
from config import INSURANCE_PRODUCTS
st.set_page_config(page_title="动态定价与核保", page_icon="💰", layout="wide")
st.markdown("# 💰 科创特有风险综合险 · 动态定价引擎")
st.markdown("基于六维风险评分的“千企千面”精准核保与定价。")
from utils.session_helper import render_company_selector, render_sidebar_global_company_selector
with st.sidebar:
render_sidebar_global_company_selector()
st.markdown("---")
# 企业选择(全局同步)
company = render_company_selector("🏢 选择目标企业", key_suffix="pricing_page")
stock_code = company["stock_code"] if company else "688256"
if company:
# 计算风险评分
risk_result = calculate_six_dimension_scores(company)
scores = risk_result["scores"]
comprehensive = risk_result["comprehensive_score"]
level_info = risk_result["risk_level"]
revenue = company.get("financials", {}).get("revenue_2024", 0)
sector = company.get("sector", "")
scale = get_enterprise_scale(revenue)
st.markdown("---")
# ============================================================
# 风险概要
# ============================================================
col_info, col_score = st.columns([2, 1])
with col_info:
st.markdown(f"### {company['short_name']}")
st.markdown(f"**行业**: {company['industry']} | **领域**: {sector} | **规模**: {scale}")
st.markdown(f"**营收**: ¥{revenue/1e8:.1f}亿 | **行业风险系数**: {INDUSTRY_RISK_FACTORS.get(sector, 1.0):.2f}")
with col_score:
color = level_info["color"]
st.markdown(
f"<div style='background:{color}22; padding:20px; border-radius:12px; text-align:center; "
f"border:2px solid {color};'>"
f"<div style='font-size:2.5em; font-weight:bold; color:{color};'>{comprehensive}</div>"
f"<div style='color:{color};'>综合风险评分 {level_info['emoji']} {level_info['level']}</div>"
f"</div>",
unsafe_allow_html=True,
)
st.markdown("---")
# ============================================================
# 保险产品费率计算
# ============================================================
st.markdown("### 📊 保险产品费率方案")
pricing_results = calculate_all_products(
comprehensive, scores, sector, revenue
)
# 三列展示三个险种
cols = st.columns(3)
for i, pricing in enumerate(pricing_results):
with cols[i]:
product = INSURANCE_PRODUCTS[pricing["product_key"]]
is_insurable = pricing["is_insurable"]
if is_insurable:
border_color = "#4CAF50" if comprehensive < 40 else ("#FF9800" if comprehensive < 70 else "#F44336")
else:
border_color = "#B71C1C"
st.markdown(
f"<div style='background:#16213e; padding:20px; border-radius:12px; "
f"border:2px solid {border_color}; min-height: 300px;'>"
f"<h4 style='text-align:center; color:white;'>{product['name']}</h4>"
f"<p style='color:#a8a8b3; font-size:0.85em; text-align:center;'>{product['description']}</p>"
f"</div>",
unsafe_allow_html=True,
)
if is_insurable:
st.metric("基础保费", f{pricing['base_premium']:,.0f}")
st.metric("最终保费", f{pricing['final_premium']:,.0f}",
delta=f"×{pricing['risk_multiplier']:.2f}",
delta_color="inverse" if pricing['risk_multiplier'] > 1.2 else "normal")
st.metric("保额", f{pricing['adjusted_coverage']:,.0f}")
st.metric("免赔率", f"{pricing['deductible_rate']:.0%}")
with st.expander("📐 定价明细"):
st.markdown(f"- 风险系数: {pricing['risk_multiplier']:.3f}")
st.markdown(f"- 行业调整: {pricing['industry_factor']:.3f}")
st.markdown(f"- 规模折扣: {pricing['scale_factor']:.3f}")
st.markdown(f"- 维度调整: {pricing['dimension_adjustment']:.3f}")
st.markdown(f"- **计算公式**: {pricing['pricing_breakdown']}")
else:
st.error("⛔ 风险过高,建议拒保")
# ============================================================
# 费率对比图
# ============================================================
st.markdown("---")
st.markdown("### 📈 费率构成分析")
col_chart1, col_chart2 = st.columns(2)
with col_chart1:
# 基础保费 vs 最终保费对比
product_names = [p["product_name"] for p in pricing_results if p["is_insurable"]]
base_premiums = [p["base_premium"] for p in pricing_results if p["is_insurable"]]
final_premiums = [p["final_premium"] for p in pricing_results if p["is_insurable"]]
max_val = max(max(base_premiums, default=100000), max(final_premiums, default=100000))
fig = go.Figure(data=[
go.Bar(
name="基础保费",
x=product_names,
y=base_premiums,
marker_color="#3B82F6",
text=[f{v:,.0f}" for v in base_premiums],
textposition="outside",
textfont=dict(size=11, color="#93C5FD")
),
go.Bar(
name="调整后保费",
x=product_names,
y=final_premiums,
marker_color="#EF4444",
text=[f{v:,.0f}" for v in final_premiums],
textposition="outside",
textfont=dict(size=11, color="#FCA5A5")
),
])
fig.update_layout(
title=dict(
text="<b>📊 基础保费 vs 调整后保费对比</b>",
font=dict(size=15, color="#F8FAFC"),
x=0.02,
y=0.96
),
barmode="group",
height=380,
margin=dict(l=20, r=20, t=75, b=30),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
font=dict(color="white"),
legend=dict(
x=0.02,
y=0.88,
orientation="h",
bgcolor="rgba(0,0,0,0)",
font=dict(color="#CBD5E1", size=12)
),
xaxis=dict(gridcolor="rgba(255,255,255,0.05)"),
yaxis=dict(
gridcolor="rgba(255,255,255,0.08)",
range=[0, max_val * 1.3]
),
)
st.plotly_chart(fig, use_container_width=True)
with col_chart2:
# 定价因子贡献瀑布图(选第一个可投保的产品)
insurable = [p for p in pricing_results if p["is_insurable"]]
if insurable:
p = insurable[0]
factors = ["基础保费", "风险系数", "行业调整", "规模折扣", "维度调整", "最终保费"]
values = [
p["base_premium"],
p["base_premium"] * (p["risk_multiplier"] - 1),
p["base_premium"] * p["risk_multiplier"] * (p["industry_factor"] - 1),
p["base_premium"] * p["risk_multiplier"] * p["industry_factor"] * (p["scale_factor"] - 1),
p["base_premium"] * p["risk_multiplier"] * p["industry_factor"] * p["scale_factor"] * (p["dimension_adjustment"] - 1),
p["final_premium"],
]
measures = ["absolute", "relative", "relative", "relative", "relative", "total"]
fig2 = go.Figure(go.Waterfall(
name=p["product_name"],
orientation="v",
measure=measures,
x=factors,
y=values,
text=[f{v:,.0f}" for v in values],
textposition="outside",
connector={"line": {"color": "rgba(255,255,255,0.3)"}},
increasing={"marker": {"color": "#EF4444"}},
decreasing={"marker": {"color": "#10B981"}},
totals={"marker": {"color": "#3B82F6"}},
))
fig2.update_layout(
title=dict(
text=f"<b>📉 {p['product_name']} — 定价因子分解</b>",
font=dict(size=15, color="#F8FAFC"),
x=0.02,
y=0.96
),
height=380,
margin=dict(l=20, r=20, t=75, b=30),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
font=dict(color="white"),
xaxis=dict(gridcolor="rgba(255,255,255,0.05)"),
yaxis=dict(gridcolor="rgba(255,255,255,0.08)"),
)
st.plotly_chart(fig2, use_container_width=True)
# ============================================================
# 核保报告生成
# ============================================================
st.markdown("---")
st.markdown("### 📋 核保决策报告")
# 检查 session_state 中是否有该企业的真实大模型辩论结果
has_real_debate = False
debate_res_in_session = st.session_state.get("debate_results")
if debate_res_in_session and debate_res_in_session.get("company", {}).get("stock_code") == stock_code:
has_real_debate = True
st.success("🤖 **已成功链接页面 3 的【大模型多智能体辩论】真实研判与穿透凭据!**")
else:
st.info("💡 **提示**:建议先至 **【⚖️ 多智能体辩论诊断】** 页面为该企业发起大模型辩论,本报告将自动整合最深度的 AI 审查凭据。")
if st.button("📄 生成完整核保报告", type="primary", use_container_width=True):
if has_real_debate:
debate_result = debate_res_in_session
else:
# 若尚未发起辩论,则回退到离线规则引擎评估
from agents.law_agent import LawAgent
from agents.tech_agent import TechAgent
from agents.finance_agent import FinanceAgent
from agents.judge_agent import JudgeAgent
law_result = LawAgent()._rule_based_evaluation(company)
tech_result = TechAgent()._rule_based_evaluation(company)
fin_result = FinanceAgent()._rule_based_evaluation(company)
judge_result = JudgeAgent()._rule_based_evaluation(
company, law_result, tech_result, fin_result
)
debate_result = {
"law_result": law_result,
"tech_result": tech_result,
"finance_result": fin_result,
"judge_result": judge_result,
"conflicts": [],
}
report = generate_report(company, debate_result, pricing_results)
markdown_report = format_report_markdown(report)
st.markdown(markdown_report)
# 下载按钮
st.download_button(
label="📥 下载核保报告 (Markdown 格式)",
data=markdown_report,
file_name=f"核保报告_{company['short_name']}_{report['report_id']}.md",
mime="text/markdown",
use_container_width=True,
)