# -*- 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 import plotly.express as px from collectors.financial_collector import get_all_companies, get_company_by_code from risk_engine.risk_scorer import calculate_six_dimension_scores, get_risk_level from utils.session_helper import render_company_selector, render_sidebar_global_company_selector st.set_page_config(page_title="企业风险概览", page_icon="📊", layout="wide") with st.sidebar: render_sidebar_global_company_selector() st.markdown("---") st.markdown("# 📊 企业风险概览") st.markdown("选择一家科创企业,查看其六维风险画像和关键指标。") # 企业选择(全局同步) company = render_company_selector("🏢 选择目标企业", key_suffix="overview_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"] # ============================================================ # 企业信息 + 综合评分 # ============================================================ col1, col2 = st.columns([2, 1]) with col1: st.markdown(f"### {company['short_name']}") st.markdown(f"**行业**: {company['industry']} | **领域**: {company['sector']} | **代码**: {company['stock_code']}") st.markdown(f"**简介**: {company['description']}") # 核心人员 st.markdown("#### 👤 核心技术人员") for p in company.get("core_tech_personnel", []): status_emoji = "✅" if "在职" in p["status"] else "⚠️" st.markdown(f"- {status_emoji} **{p['name']}** ({p['title']}) - 重要性: {p['importance']} - 状态: {p['status']}") with col2: # 综合风险仪表盘 fig = go.Figure(go.Indicator( mode="gauge+number", value=comprehensive, title={"text": "综合风险评分", "font": {"color": "white"}}, number={"font": {"color": "white", "size": 48}}, gauge={ "axis": {"range": [0, 100], "tickcolor": "white"}, "bar": {"color": level_info["color"]}, "steps": [ {"range": [0, 30], "color": "rgba(76,175,80,0.3)"}, {"range": [30, 50], "color": "rgba(255,193,7,0.3)"}, {"range": [50, 70], "color": "rgba(255,152,0,0.3)"}, {"range": [70, 100], "color": "rgba(244,67,54,0.3)"}, ], "threshold": { "line": {"color": "red", "width": 4}, "thickness": 0.75, "value": 70, }, }, )) fig.update_layout( height=250, margin=dict(l=20, r=20, t=40, b=10), paper_bgcolor="rgba(0,0,0,0)", font=dict(color="white"), ) st.plotly_chart(fig, use_container_width=True) st.markdown(f"
" f"{level_info['emoji']} 风险等级: {level_info['level']}
", unsafe_allow_html=True) st.markdown("---") # ============================================================ # 六维风险雷达图 # ============================================================ col_radar, col_detail = st.columns([1, 1]) with col_radar: st.markdown("#### 🎯 六维风险雷达图") dim_names_cn = ["技术路线颠覆", "核心人员流失", "算法/数据合规", "地缘政治/出口管制", "研发资本化操纵", "客户/供应商集中"] dim_keys = ["tech_disruption", "talent_loss", "algo_compliance", "geopolitical", "rd_capitalization", "concentration"] values = [scores[k] for k in dim_keys] fig = go.Figure() fig.add_trace(go.Scatterpolar( r=values + [values[0]], theta=dim_names_cn + [dim_names_cn[0]], fill="toself", fillcolor="rgba(233,69,96,0.3)", line=dict(color="#e94560", width=2), marker=dict(size=8, color="#e94560"), name=company["short_name"], )) # 添加警戒线 fig.add_trace(go.Scatterpolar( r=[70] * 7, theta=dim_names_cn + [dim_names_cn[0]], line=dict(color="rgba(244,67,54,0.5)", dash="dash", width=1), name="高风险线(70)", )) fig.update_layout( polar=dict( radialaxis=dict(visible=True, range=[0, 100], tickfont=dict(color="white")), angularaxis=dict(tickfont=dict(color="white", size=11)), bgcolor="rgba(0,0,0,0)", ), height=420, margin=dict(l=60, r=60, t=30, b=30), paper_bgcolor="rgba(0,0,0,0)", font=dict(color="white"), showlegend=True, legend=dict(x=0, y=-0.15), ) st.plotly_chart(fig, use_container_width=True) with col_detail: st.markdown("#### 📋 各维度风险详情") for k in dim_keys: detail = risk_result["dimension_details"][k] score = detail["score"] level = detail["level"] emoji = level["emoji"] color = level["color"] st.markdown( f"
" f"{emoji} {detail['name']}: {score}分 ({level['level']})
", unsafe_allow_html=True, ) st.markdown("---") # ============================================================ # 关键财务指标 # ============================================================ st.markdown("#### 💰 关键财务指标") fin = company.get("financials", {}) col_f1, col_f2, col_f3, col_f4 = st.columns(4) with col_f1: revenue = fin.get("revenue_2024", 0) st.metric("营业收入", f"¥{revenue/1e8:.1f}亿") with col_f2: profit = fin.get("net_profit_2024", 0) st.metric("净利润", f"¥{profit/1e8:.1f}亿", delta="盈利" if profit > 0 else "亏损", delta_color="normal" if profit > 0 else "inverse") with col_f3: rd = fin.get("rd_expense_2024", 0) st.metric("研发费用", f"¥{rd/1e8:.1f}亿") with col_f4: cap_rate = fin.get("rd_capitalization_rate", 0) st.metric("研发资本化率", f"{cap_rate:.0%}", delta="⚠️ 偏高" if cap_rate > 0.3 else "正常", delta_color="inverse" if cap_rate > 0.3 else "normal") col_f5, col_f6, col_f7, col_f8 = st.columns(4) with col_f5: st.metric("研发/营收比", f"{fin.get('rd_revenue_ratio', 0):.1%}") with col_f6: st.metric("前5大客户占比", f"{fin.get('top5_customer_ratio', 0):.0%}") with col_f7: st.metric("应收周转率", f"{fin.get('receivable_turnover', 0):.1f}次/年") with col_f8: st.metric("现金流比率", f"{fin.get('cash_flow_ratio', 0):.2f}") # ============================================================ # 技术路线 & 合规信息 # ============================================================ st.markdown("---") col_tech, col_comp = st.columns(2) with col_tech: st.markdown("#### 🔬 技术路线") tech = company.get("tech_route", {}) st.markdown(f"**当前技术**: {tech.get('current_tech', '未知')}") st.markdown(f"**技术壁垒**: {tech.get('tech_moat', '未知')}") st.markdown(f"**专利数量**: {tech.get('patent_count', 0)} 件") st.markdown("**竞争技术路线**:") for ct in tech.get("competing_techs", []): st.markdown(f" - ⚔️ {ct}") with col_comp: st.markdown("#### 📋 合规状态") comp_info = company.get("compliance", {}) st.markdown(f"**算法备案**: {comp_info.get('algo_filing_status', '未知')}") st.markdown(f"**数据出境风险**: {comp_info.get('data_export_risk', '未知')}") entity_status = comp_info.get("entity_list_status", "未知") if "被列入" in entity_status: st.error(f"⛔ 实体清单: {entity_status}") if comp_info.get("sanctions_detail"): st.warning(f"制裁详情: {comp_info['sanctions_detail']}") else: st.success(f"✅ 实体清单: {entity_status}")