feat: 初始提交 - 科创企业特有风险的识别与管理 (数智风控系统)
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""多智能体辩论模块"""
|
||||
@@ -0,0 +1,203 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Agent 基类
|
||||
支持逐 Token 实时流式打字机输出 (Token-level UI Streaming)
|
||||
捕获思维链 (reasoning_content) 与最终生成结果 (content) 逐字推送到前端
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, Callable
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseAgent:
|
||||
"""智能体基类,支持 Token 级流式 LLM 推理与降级逻辑"""
|
||||
|
||||
def __init__(self, name: str, system_prompt: str, role_icon: str = "🤖"):
|
||||
self.name = name
|
||||
self.system_prompt = system_prompt
|
||||
self.role_icon = role_icon
|
||||
self._client = None
|
||||
# 推理链记录
|
||||
self.reasoning_trace = []
|
||||
# 逐 Token 实时回调函数: callback(token_type: "reasoning"|"content", token_text: str)
|
||||
self.on_token_callback: Optional[Callable[[str, str], None]] = None
|
||||
|
||||
def _get_client(self):
|
||||
"""延迟初始化 OpenAI 客户端"""
|
||||
if self._client is not None:
|
||||
return self._client
|
||||
|
||||
try:
|
||||
import httpx
|
||||
from openai import OpenAI
|
||||
import os
|
||||
|
||||
from config import (
|
||||
DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL,
|
||||
VOLCENGINE_API_KEY, VOLCENGINE_BASE_URL, VOLCENGINE_MODEL,
|
||||
OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL
|
||||
)
|
||||
|
||||
api_key = os.environ.get("DEEPSEEK_API_KEY", "")
|
||||
if api_key:
|
||||
base_url = DEEPSEEK_BASE_URL
|
||||
model = DEEPSEEK_MODEL
|
||||
else:
|
||||
api_key = VOLCENGINE_API_KEY
|
||||
base_url = VOLCENGINE_BASE_URL
|
||||
model = VOLCENGINE_MODEL
|
||||
|
||||
if not api_key:
|
||||
api_key = OPENAI_API_KEY
|
||||
base_url = OPENAI_BASE_URL
|
||||
model = OPENAI_MODEL
|
||||
|
||||
if api_key:
|
||||
http_client = httpx.Client(trust_env=False, timeout=60.0)
|
||||
self._client = OpenAI(
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
http_client=http_client,
|
||||
)
|
||||
self._model = model
|
||||
logger.info(f"[{self.name}] 已成功连接大模型服务")
|
||||
return self._client
|
||||
except ImportError:
|
||||
logger.warning("openai 库未安装")
|
||||
except Exception as e:
|
||||
logger.warning(f"初始化 LLM 客户端失败: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def _trace(self, step: str, content: str):
|
||||
"""记录推理链步骤"""
|
||||
entry = {
|
||||
"timestamp": time.strftime("%H:%M:%S"),
|
||||
"step": step,
|
||||
"content": content,
|
||||
"agent": self.name,
|
||||
"icon": self.role_icon
|
||||
}
|
||||
self.reasoning_trace.append(entry)
|
||||
|
||||
def infer(self, prompt: str, temperature: float = 0.1, max_retries: int = 0) -> str:
|
||||
"""
|
||||
执行 SSE 流式 LLM 推理 (stream=True)
|
||||
逐 Token 实时推送到 on_token_callback 渲染打字机效果
|
||||
"""
|
||||
self.reasoning_trace = []
|
||||
self._trace("📝 构建 Context", f"准备【{self.name}】数据与 Prompt")
|
||||
|
||||
client = self._get_client()
|
||||
if client is None:
|
||||
self._trace("⚠️ 状态通知", "大模型未就绪,切换至专家规则引擎")
|
||||
logger.info(f"[{self.name}] LLM 不可用,降级到规则引擎")
|
||||
fallback = self.fallback_inference(prompt)
|
||||
self._trace("🔧 专家引擎输出", fallback)
|
||||
return fallback
|
||||
|
||||
self._trace("🔗 大模型连接", "已连接大模型推理服务")
|
||||
|
||||
for attempt in range(max_retries + 1):
|
||||
try:
|
||||
self._trace("🚀 发起流式推理", "正在建立 SSE 流式传输通道...")
|
||||
t0 = time.time()
|
||||
|
||||
stream_resp = client.chat.completions.create(
|
||||
model=self._model,
|
||||
messages=[
|
||||
{"role": "system", "content": self.system_prompt},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=temperature,
|
||||
max_tokens=2048,
|
||||
stream=True,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
full_content = []
|
||||
reasoning_chunks = []
|
||||
|
||||
for chunk in stream_resp:
|
||||
if not chunk.choices:
|
||||
continue
|
||||
delta = chunk.choices[0].delta
|
||||
|
||||
# 1. 逐 Token 提取深度思考过程 (reasoning_content)
|
||||
reasoning_piece = getattr(delta, "reasoning_content", None) or getattr(delta, "reasoning", None)
|
||||
if reasoning_piece:
|
||||
reasoning_chunks.append(reasoning_piece)
|
||||
if self.on_token_callback:
|
||||
try:
|
||||
self.on_token_callback("reasoning", reasoning_piece)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. 逐 Token 提取正式回答内容 (content)
|
||||
content_piece = delta.content
|
||||
if content_piece:
|
||||
full_content.append(content_piece)
|
||||
if self.on_token_callback:
|
||||
try:
|
||||
self.on_token_callback("content", content_piece)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
elapsed = time.time() - t0
|
||||
final_text = "".join(full_content)
|
||||
full_reasoning = "".join(reasoning_chunks)
|
||||
|
||||
if full_reasoning:
|
||||
self._trace("🧠 完整思维链", full_reasoning)
|
||||
|
||||
if final_text.strip():
|
||||
self._trace("✅ 流式生成完毕", f"耗时 {elapsed:.1f}s | 产出 {len(final_text)} 字符")
|
||||
self._trace("📄 原始推理输出", final_text)
|
||||
return final_text
|
||||
else:
|
||||
self._trace("⚠️ 输出为空", "流式生成无有效内容,降级到专家引擎")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
self._trace("⚡ 流式传输异常", f"连接中断: {error_msg}")
|
||||
logger.warning(f"[{self.name}] 流式调用失败: {e}")
|
||||
|
||||
self._trace("🛡️ 安全降级", "无缝切换至离线风控规则引擎")
|
||||
fallback = self.fallback_inference(prompt)
|
||||
self._trace("🔧 专家引擎输出", fallback)
|
||||
return fallback
|
||||
|
||||
def infer_json(self, prompt: str, temperature: float = 0.1) -> dict:
|
||||
"""
|
||||
执行 LLM 推理并解析为 JSON
|
||||
"""
|
||||
result = self.infer(prompt, temperature)
|
||||
try:
|
||||
if "```json" in result:
|
||||
json_str = result.split("```json")[1].split("```")[0].strip()
|
||||
parsed = json.loads(json_str)
|
||||
self._trace("✅ 结构解析", "从 Markdown 成功提取 JSON 数据")
|
||||
return parsed
|
||||
elif "```" in result:
|
||||
json_str = result.split("```")[1].split("```")[0].strip()
|
||||
parsed = json.loads(json_str)
|
||||
self._trace("✅ 结构解析", "从代码块成功提取 JSON 数据")
|
||||
return parsed
|
||||
else:
|
||||
parsed = json.loads(result)
|
||||
self._trace("✅ 结构解析", "直接解析 JSON 成功")
|
||||
return parsed
|
||||
except (json.JSONDecodeError, IndexError):
|
||||
self._trace("⚠️ 格式适配", "启用自动结构修正")
|
||||
logger.warning(f"[{self.name}] JSON 解析失败,返回原始文本")
|
||||
return {"raw_response": result, "parse_error": True}
|
||||
|
||||
def fallback_inference(self, prompt: str) -> str:
|
||||
"""规则引擎降级推理"""
|
||||
return json.dumps({"error": "大模型服务不可用,规则引擎未实现"}, ensure_ascii=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.role_icon} {self.name}"
|
||||
@@ -0,0 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
多智能体辩论编排引擎
|
||||
流程:信息分发 → 独立研判 → 交叉质证 → 综合裁决
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from .law_agent import LawAgent
|
||||
from .tech_agent import TechAgent
|
||||
from .finance_agent import FinanceAgent
|
||||
from .judge_agent import JudgeAgent
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DebateEngine:
|
||||
"""多智能体辩论编排器"""
|
||||
|
||||
def __init__(self):
|
||||
self.law_agent = LawAgent()
|
||||
self.tech_agent = TechAgent()
|
||||
self.finance_agent = FinanceAgent()
|
||||
self.judge_agent = JudgeAgent()
|
||||
self.debate_log = []
|
||||
|
||||
def run_debate(self, company_data: dict, callback=None) -> dict:
|
||||
"""
|
||||
执行完整的多智能体辩论流程
|
||||
|
||||
Args:
|
||||
company_data: 企业数据字典
|
||||
callback: 进度回调函数 callback(step, message, result)
|
||||
"""
|
||||
self.debate_log = []
|
||||
company_name = company_data.get("short_name", company_data.get("company_name", "未知"))
|
||||
start_time = time.time()
|
||||
|
||||
self._log(f"🏁 启动对 [{company_name}] 的多智能体交叉验证辩论")
|
||||
|
||||
# ==============================
|
||||
# Phase 1: 独立研判
|
||||
# ==============================
|
||||
self._log("=" * 50)
|
||||
self._log("📋 Phase 1: 各节点独立研判")
|
||||
self._log("=" * 50)
|
||||
|
||||
# 法务节点
|
||||
self._log("👩⚖️ 法务风控节点开始评估...")
|
||||
if callback:
|
||||
callback("law_start", "法务风控节点开始评估...", None)
|
||||
law_result = self.law_agent.evaluate(company_data)
|
||||
self._log(f"👩⚖️ 法务节点完成: 综合法务风险 {law_result.get('overall_law_risk', {}).get('score', '?')} 分")
|
||||
if callback:
|
||||
callback("law_done", "法务风控节点评估完成", law_result)
|
||||
|
||||
# 技术节点
|
||||
self._log("👨🔬 技术风控节点开始评估...")
|
||||
if callback:
|
||||
callback("tech_start", "技术风控节点开始评估...", None)
|
||||
tech_result = self.tech_agent.evaluate(company_data)
|
||||
self._log(f"👨🔬 技术节点完成: 综合技术风险 {tech_result.get('overall_tech_risk', {}).get('score', '?')} 分")
|
||||
if callback:
|
||||
callback("tech_done", "技术风控节点评估完成", tech_result)
|
||||
|
||||
# 财务节点
|
||||
self._log("👔 财务风控节点开始评估...")
|
||||
if callback:
|
||||
callback("fin_start", "财务风控节点开始评估...", None)
|
||||
finance_result = self.finance_agent.evaluate(company_data)
|
||||
self._log(f"👔 财务节点完成: 综合财务风险 {finance_result.get('overall_fin_risk', {}).get('score', '?')} 分")
|
||||
if callback:
|
||||
callback("fin_done", "财务风控节点评估完成", finance_result)
|
||||
|
||||
# ==============================
|
||||
# Phase 2: 交叉质证(记录冲突点)
|
||||
# ==============================
|
||||
self._log("=" * 50)
|
||||
self._log("🔄 Phase 2: 交叉质证")
|
||||
self._log("=" * 50)
|
||||
|
||||
conflicts = self._identify_conflicts(law_result, tech_result, finance_result)
|
||||
for conflict in conflicts:
|
||||
self._log(f"⚠️ 冲突: {conflict}")
|
||||
if not conflicts:
|
||||
self._log("✅ 各节点意见一致,无冲突")
|
||||
|
||||
if callback:
|
||||
callback("cross_validation", "交叉质证完成", {"conflicts": conflicts})
|
||||
|
||||
# ==============================
|
||||
# Phase 3: 综合裁决
|
||||
# ==============================
|
||||
self._log("=" * 50)
|
||||
self._log("⚖️ Phase 3: 综合裁决")
|
||||
self._log("=" * 50)
|
||||
|
||||
if callback:
|
||||
callback("judge_start", "综合裁决节点开始...", None)
|
||||
judge_result = self.judge_agent.evaluate(
|
||||
company_data, law_result, tech_result, finance_result
|
||||
)
|
||||
self._log(f"⚖️ 综合评分: {judge_result.get('comprehensive_score', '?')} 分")
|
||||
self._log(f"⚖️ 核保建议: 【{judge_result.get('underwriting_decision', '?')}】")
|
||||
if callback:
|
||||
callback("judge_done", "综合裁决完成", judge_result)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
self._log(f"🏁 辩论完成,耗时 {elapsed:.1f} 秒")
|
||||
|
||||
return {
|
||||
"company": company_name,
|
||||
"law_result": law_result,
|
||||
"tech_result": tech_result,
|
||||
"finance_result": finance_result,
|
||||
"conflicts": conflicts,
|
||||
"judge_result": judge_result,
|
||||
"debate_log": self.debate_log,
|
||||
"elapsed_seconds": round(elapsed, 1),
|
||||
}
|
||||
|
||||
def _identify_conflicts(self, law_result: dict, tech_result: dict, finance_result: dict) -> list:
|
||||
"""识别各节点之间的判定冲突"""
|
||||
conflicts = []
|
||||
|
||||
# 检查法务和技术的冲突:例如法务认为合规但技术认为路线有风险
|
||||
law_overall = law_result.get("overall_law_risk", {}).get("score", 50)
|
||||
tech_overall = tech_result.get("overall_tech_risk", {}).get("score", 50)
|
||||
fin_overall = finance_result.get("overall_fin_risk", {}).get("score", 50)
|
||||
|
||||
# 大幅分歧(差异超过30分)
|
||||
if abs(law_overall - tech_overall) > 30:
|
||||
if law_overall > tech_overall:
|
||||
conflicts.append(
|
||||
f"法务节点({law_overall}分)与技术节点({tech_overall}分)存在较大分歧: "
|
||||
f"法务认为合规风险较高,但技术面评估相对乐观"
|
||||
)
|
||||
else:
|
||||
conflicts.append(
|
||||
f"技术节点({tech_overall}分)与法务节点({law_overall}分)存在较大分歧: "
|
||||
f"技术风险较高,但法务合规状态相对可控"
|
||||
)
|
||||
|
||||
if abs(tech_overall - fin_overall) > 30:
|
||||
conflicts.append(
|
||||
f"技术节点({tech_overall}分)与财务节点({fin_overall}分)存在分歧: "
|
||||
f"需审查技术投入与财务表现的匹配度"
|
||||
)
|
||||
|
||||
# 特定维度冲突:技术认为研发投入大=好事,财务可能认为是资本化操纵
|
||||
tech_rd_view = tech_result.get("tech_iteration_pressure", {}).get("score", 50)
|
||||
fin_rd_view = finance_result.get("rd_capitalization_risk", {}).get("score", 50)
|
||||
if tech_rd_view < 40 and fin_rd_view > 60:
|
||||
conflicts.append(
|
||||
"技术节点认为研发投入合理,但财务节点发现研发资本化率异常,"
|
||||
"存在通过资本化手段美化利润的嫌疑"
|
||||
)
|
||||
|
||||
return conflicts
|
||||
|
||||
def _log(self, message: str):
|
||||
"""记录辩论日志"""
|
||||
entry = {"timestamp": time.strftime("%H:%M:%S"), "message": message}
|
||||
self.debate_log.append(entry)
|
||||
logger.info(message)
|
||||
|
||||
|
||||
def run_debate(stock_code: str) -> dict:
|
||||
"""便捷接口:通过股票代码直接运行辩论"""
|
||||
from collectors.financial_collector import get_company_by_code
|
||||
company_data = get_company_by_code(stock_code)
|
||||
if not company_data:
|
||||
return {"error": f"未找到股票代码 {stock_code} 的企业数据"}
|
||||
|
||||
engine = DebateEngine()
|
||||
return engine.run_debate(company_data)
|
||||
@@ -0,0 +1,174 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
财务风控节点
|
||||
审查维度:研发资本化操纵、客户/供应商集中度、应收账款质量、现金流
|
||||
"""
|
||||
import json
|
||||
from .base_agent import BaseAgent
|
||||
|
||||
|
||||
FIN_SYSTEM_PROMPT = """你是一名精通科创板审计规则的注册会计师(CPA),同时是金融风控专家。
|
||||
|
||||
你的任务是基于提供的企业财务数据,从财务角度穿透审查以下风险:
|
||||
1. 研发资本化操纵风险:研发资本化率是否异常,是否存在美化利润嫌疑
|
||||
2. 客户/供应商集中风险:前五大客户/供应商占比是否过高
|
||||
3. 应收账款质量:应收账款周转率是否异常,是否存在坏账风险
|
||||
4. 现金流健康度:经营现金流是否能覆盖运营需求
|
||||
|
||||
评估标准参考:
|
||||
- 科创板企业研发资本化率超过30%需重点关注
|
||||
- 前五大客户占比超过50%存在集中风险
|
||||
- 应收账款周转率低于4次/年需关注回款能力
|
||||
- 经营现金流/营收比低于0.5需关注持续经营能力
|
||||
|
||||
请输出JSON格式:
|
||||
{
|
||||
"agent": "财务风控节点",
|
||||
"rd_capitalization_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"concentration_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"receivable_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"cashflow_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"overall_fin_risk": {"score": 0-100, "level": "高/中/低"},
|
||||
"key_findings": ["..."],
|
||||
"recommendations": ["..."]
|
||||
}"""
|
||||
|
||||
|
||||
class FinanceAgent(BaseAgent):
|
||||
"""财务风控节点"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name="财务风控节点",
|
||||
system_prompt=FIN_SYSTEM_PROMPT,
|
||||
role_icon="👔",
|
||||
)
|
||||
|
||||
def evaluate(self, company_data: dict) -> dict:
|
||||
"""执行财务风险评估"""
|
||||
prompt = self._build_prompt(company_data)
|
||||
result = self.infer_json(prompt)
|
||||
|
||||
if result.get("parse_error"):
|
||||
result = self._rule_based_evaluation(company_data)
|
||||
|
||||
result["agent"] = "财务风控节点"
|
||||
result["icon"] = self.role_icon
|
||||
return result
|
||||
|
||||
def _build_prompt(self, company_data: dict) -> str:
|
||||
financials = company_data.get("financials", {})
|
||||
return f"""请对以下科创企业进行财务风险穿透审查:
|
||||
|
||||
企业名称:{company_data.get('short_name', '未知')}
|
||||
行业:{company_data.get('industry', '未知')}
|
||||
|
||||
财务核心指标:
|
||||
- 营业收入: {financials.get('revenue_2024', 0):,.0f} 元
|
||||
- 净利润: {financials.get('net_profit_2024', 0):,.0f} 元
|
||||
- 研发费用: {financials.get('rd_expense_2024', 0):,.0f} 元
|
||||
- 研发资本化率: {financials.get('rd_capitalization_rate', 0):.1%}
|
||||
- 研发/营收比: {financials.get('rd_revenue_ratio', 0):.1%}
|
||||
- 前五大客户占比: {financials.get('top5_customer_ratio', 0):.1%}
|
||||
- 前五大供应商占比: {financials.get('top5_supplier_ratio', 0):.1%}
|
||||
- 应收账款周转率: {financials.get('receivable_turnover', 0):.1f} 次/年
|
||||
- 经营现金流比率: {financials.get('cash_flow_ratio', 0):.2f}
|
||||
|
||||
请输出严格的JSON评估结果。"""
|
||||
|
||||
def _rule_based_evaluation(self, company_data: dict) -> dict:
|
||||
"""基于规则的财务风险评估"""
|
||||
fin = company_data.get("financials", {})
|
||||
|
||||
# 1. 研发资本化操纵风险
|
||||
cap_rate = fin.get("rd_capitalization_rate", 0)
|
||||
if cap_rate >= 0.4:
|
||||
rd_score = 90
|
||||
rd_detail = f"研发资本化率高达{cap_rate:.0%},严重怀疑美化利润"
|
||||
elif cap_rate >= 0.3:
|
||||
rd_score = 70
|
||||
rd_detail = f"研发资本化率{cap_rate:.0%},超过行业警戒线(30%),需重点审查"
|
||||
elif cap_rate >= 0.15:
|
||||
rd_score = 45
|
||||
rd_detail = f"研发资本化率{cap_rate:.0%},处于中等水平,建议关注趋势"
|
||||
elif cap_rate > 0:
|
||||
rd_score = 25
|
||||
rd_detail = f"研发资本化率{cap_rate:.0%},处于合理范围"
|
||||
else:
|
||||
rd_score = 10
|
||||
rd_detail = "研发费用全部费用化处理,财务政策审慎"
|
||||
|
||||
# 2. 集中度风险
|
||||
customer_ratio = fin.get("top5_customer_ratio", 0)
|
||||
supplier_ratio = fin.get("top5_supplier_ratio", 0)
|
||||
max_concentration = max(customer_ratio, supplier_ratio)
|
||||
|
||||
if max_concentration >= 0.8:
|
||||
conc_score = 90
|
||||
conc_detail = f"前五大客户占比{customer_ratio:.0%},供应商占比{supplier_ratio:.0%},集中度极高"
|
||||
elif max_concentration >= 0.6:
|
||||
conc_score = 70
|
||||
conc_detail = f"前五大客户占比{customer_ratio:.0%},供应商占比{supplier_ratio:.0%},集中度偏高"
|
||||
elif max_concentration >= 0.4:
|
||||
conc_score = 45
|
||||
conc_detail = f"前五大客户占比{customer_ratio:.0%},供应商占比{supplier_ratio:.0%},中等集中度"
|
||||
else:
|
||||
conc_score = 20
|
||||
conc_detail = f"客户和供应商分布较为分散"
|
||||
|
||||
# 3. 应收账款风险
|
||||
turnover = fin.get("receivable_turnover", 8)
|
||||
if turnover < 3:
|
||||
recv_score = 80
|
||||
recv_detail = f"应收账款周转率仅{turnover:.1f}次/年,回款能力极差"
|
||||
elif turnover < 5:
|
||||
recv_score = 55
|
||||
recv_detail = f"应收账款周转率{turnover:.1f}次/年,回款速度偏慢"
|
||||
elif turnover < 8:
|
||||
recv_score = 30
|
||||
recv_detail = f"应收账款周转率{turnover:.1f}次/年,回款能力尚可"
|
||||
else:
|
||||
recv_score = 15
|
||||
recv_detail = f"应收账款周转率{turnover:.1f}次/年,回款能力良好"
|
||||
|
||||
# 4. 现金流风险
|
||||
cf_ratio = fin.get("cash_flow_ratio", 1.0)
|
||||
if cf_ratio < 0.5:
|
||||
cf_score = 80
|
||||
cf_detail = f"经营现金流比率仅{cf_ratio:.2f},存在持续经营风险"
|
||||
elif cf_ratio < 0.8:
|
||||
cf_score = 55
|
||||
cf_detail = f"经营现金流比率{cf_ratio:.2f},现金流偏紧"
|
||||
elif cf_ratio < 1.2:
|
||||
cf_score = 30
|
||||
cf_detail = f"经营现金流比率{cf_ratio:.2f},基本健康"
|
||||
else:
|
||||
cf_score = 15
|
||||
cf_detail = f"经营现金流比率{cf_ratio:.2f},现金流充裕"
|
||||
|
||||
overall = int(rd_score * 0.30 + conc_score * 0.30 +
|
||||
recv_score * 0.20 + cf_score * 0.20)
|
||||
|
||||
findings = [rd_detail, conc_detail]
|
||||
if recv_score >= 50:
|
||||
findings.append(recv_detail)
|
||||
if cf_score >= 50:
|
||||
findings.append(cf_detail)
|
||||
|
||||
return {
|
||||
"rd_capitalization_risk": {"score": rd_score, "level": self._level(rd_score), "detail": rd_detail},
|
||||
"concentration_risk": {"score": conc_score, "level": self._level(conc_score), "detail": conc_detail},
|
||||
"receivable_risk": {"score": recv_score, "level": self._level(recv_score), "detail": recv_detail},
|
||||
"cashflow_risk": {"score": cf_score, "level": self._level(cf_score), "detail": cf_detail},
|
||||
"overall_fin_risk": {"score": overall, "level": self._level(overall)},
|
||||
"key_findings": findings,
|
||||
"recommendations": ["关注研发资本化率变化趋势", "降低客户集中度风险"],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _level(score: int) -> str:
|
||||
if score >= 70:
|
||||
return "高"
|
||||
elif score >= 40:
|
||||
return "中"
|
||||
return "低"
|
||||
@@ -0,0 +1,176 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
综合裁决节点
|
||||
汇总法务/技术/财务三方意见,消解冲突,输出最终综合评级
|
||||
"""
|
||||
import json
|
||||
from .base_agent import BaseAgent
|
||||
|
||||
|
||||
JUDGE_SYSTEM_PROMPT = """你是一名资深的风险管理委员会主席,负责汇总法务、技术、财务三方专家的研判意见。
|
||||
|
||||
你的任务是:
|
||||
1. 审阅三方专家的评估报告
|
||||
2. 识别各方意见的冲突点
|
||||
3. 基于优先级原则消解冲突(合规风险 > 技术风险 > 财务风险)
|
||||
4. 输出 0-100 的综合风险评分
|
||||
5. 给出最终核保建议
|
||||
|
||||
核保决策标准:
|
||||
- 综合风险 ≥ 80分:建议【拒绝承保】
|
||||
- 60 ≤ 综合风险 < 80分:建议【附条件承保】(高免赔额/限额)
|
||||
- 40 ≤ 综合风险 < 60分:建议【标准承保】(标准费率上浮)
|
||||
- 综合风险 < 40分:建议【优先承保】(可享费率优惠)
|
||||
|
||||
请输出JSON格式:
|
||||
{
|
||||
"comprehensive_score": 0-100,
|
||||
"risk_level": "极高/高/中/低",
|
||||
"underwriting_decision": "拒绝承保/附条件承保/标准承保/优先承保",
|
||||
"six_dimension_scores": {
|
||||
"tech_disruption": 0-100,
|
||||
"talent_loss": 0-100,
|
||||
"algo_compliance": 0-100,
|
||||
"geopolitical": 0-100,
|
||||
"rd_capitalization": 0-100,
|
||||
"concentration": 0-100
|
||||
},
|
||||
"conflict_resolution": "...",
|
||||
"key_risks": ["..."],
|
||||
"underwriting_conditions": ["..."],
|
||||
"summary": "..."
|
||||
}"""
|
||||
|
||||
|
||||
class JudgeAgent(BaseAgent):
|
||||
"""综合裁决节点"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name="综合裁决节点",
|
||||
system_prompt=JUDGE_SYSTEM_PROMPT,
|
||||
role_icon="⚖️",
|
||||
)
|
||||
|
||||
def evaluate(self, company_data: dict, law_result: dict,
|
||||
tech_result: dict, finance_result: dict) -> dict:
|
||||
"""汇总三方意见,输出综合裁决"""
|
||||
prompt = self._build_prompt(company_data, law_result, tech_result, finance_result)
|
||||
result = self.infer_json(prompt)
|
||||
|
||||
if result.get("parse_error"):
|
||||
result = self._rule_based_evaluation(company_data, law_result, tech_result, finance_result)
|
||||
|
||||
result["agent"] = "综合裁决节点"
|
||||
result["icon"] = self.role_icon
|
||||
return result
|
||||
|
||||
def _build_prompt(self, company_data: dict, law_result: dict,
|
||||
tech_result: dict, finance_result: dict) -> str:
|
||||
return f"""请对以下科创企业的三方评估结果进行综合裁决:
|
||||
|
||||
企业名称:{company_data.get('short_name', '未知')}
|
||||
行业:{company_data.get('industry', '未知')}
|
||||
|
||||
=== 👩⚖️ 法务风控节点评估 ===
|
||||
{json.dumps(law_result, ensure_ascii=False, indent=2)}
|
||||
|
||||
=== 👨🔬 技术风控节点评估 ===
|
||||
{json.dumps(tech_result, ensure_ascii=False, indent=2)}
|
||||
|
||||
=== 👔 财务风控节点评估 ===
|
||||
{json.dumps(finance_result, ensure_ascii=False, indent=2)}
|
||||
|
||||
请消解可能存在的判定冲突,输出综合裁决JSON。"""
|
||||
|
||||
def _rule_based_evaluation(self, company_data: dict, law_result: dict,
|
||||
tech_result: dict, finance_result: dict) -> dict:
|
||||
"""规则引擎综合裁决"""
|
||||
|
||||
# 提取各维度得分
|
||||
def safe_score(result: dict, key: str) -> int:
|
||||
item = result.get(key, {})
|
||||
if isinstance(item, dict):
|
||||
return item.get("score", 50)
|
||||
return 50
|
||||
|
||||
# 六维评分
|
||||
scores = {
|
||||
"tech_disruption": safe_score(tech_result, "tech_disruption_risk"),
|
||||
"talent_loss": safe_score(tech_result, "talent_loss_risk"),
|
||||
"algo_compliance": safe_score(law_result, "algo_compliance_risk"),
|
||||
"geopolitical": safe_score(law_result, "geopolitical_risk"),
|
||||
"rd_capitalization": safe_score(finance_result, "rd_capitalization_risk"),
|
||||
"concentration": safe_score(finance_result, "concentration_risk"),
|
||||
}
|
||||
|
||||
# 加权综合得分
|
||||
weights = {
|
||||
"tech_disruption": 0.20,
|
||||
"talent_loss": 0.15,
|
||||
"algo_compliance": 0.15,
|
||||
"geopolitical": 0.20,
|
||||
"rd_capitalization": 0.15,
|
||||
"concentration": 0.15,
|
||||
}
|
||||
|
||||
comprehensive_score = int(
|
||||
sum(scores[k] * weights[k] for k in scores)
|
||||
)
|
||||
|
||||
# 裁决
|
||||
if comprehensive_score >= 80:
|
||||
decision = "拒绝承保"
|
||||
risk_level = "极高"
|
||||
elif comprehensive_score >= 60:
|
||||
decision = "附条件承保"
|
||||
risk_level = "高"
|
||||
elif comprehensive_score >= 40:
|
||||
decision = "标准承保"
|
||||
risk_level = "中"
|
||||
else:
|
||||
decision = "优先承保"
|
||||
risk_level = "低"
|
||||
|
||||
# 收集关键风险
|
||||
key_risks = []
|
||||
for dim, score in sorted(scores.items(), key=lambda x: x[1], reverse=True):
|
||||
if score >= 60:
|
||||
dim_names = {
|
||||
"tech_disruption": "技术路线颠覆",
|
||||
"talent_loss": "核心人员流失",
|
||||
"algo_compliance": "算法/数据合规",
|
||||
"geopolitical": "地缘政治/出口管制",
|
||||
"rd_capitalization": "研发资本化操纵",
|
||||
"concentration": "客户/供应商集中",
|
||||
}
|
||||
key_risks.append(f"{dim_names.get(dim, dim)}风险({score}分)")
|
||||
|
||||
# 核保条件
|
||||
conditions = []
|
||||
if scores["geopolitical"] >= 70:
|
||||
conditions.append("要求提供出口管制合规声明及供应链替代方案")
|
||||
if scores["rd_capitalization"] >= 60:
|
||||
conditions.append("要求额外提供研发资本化会计政策说明及审计意见")
|
||||
if scores["concentration"] >= 60:
|
||||
conditions.append("要求提供客户分散化计划或前五大客户信用报告")
|
||||
if scores["talent_loss"] >= 60:
|
||||
conditions.append("要求核心技术人员签署竞业协议且公司有留任激励计划")
|
||||
|
||||
company_name = company_data.get("short_name", "该企业")
|
||||
summary = (
|
||||
f"{company_name}综合风险评分{comprehensive_score}分(风险等级:{risk_level})。"
|
||||
f"核保建议:【{decision}】。"
|
||||
f"主要风险集中在{'、'.join(key_risks[:3]) if key_risks else '无突出风险'}。"
|
||||
)
|
||||
|
||||
return {
|
||||
"comprehensive_score": comprehensive_score,
|
||||
"risk_level": risk_level,
|
||||
"underwriting_decision": decision,
|
||||
"six_dimension_scores": scores,
|
||||
"conflict_resolution": "基于优先级原则(合规>技术>财务)进行加权裁决",
|
||||
"key_risks": key_risks,
|
||||
"underwriting_conditions": conditions,
|
||||
"summary": summary,
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
法务风控节点
|
||||
审查维度:算法备案状态、实体清单命中、数据出境风险、知识产权诉讼
|
||||
"""
|
||||
import json
|
||||
from .base_agent import BaseAgent
|
||||
|
||||
|
||||
LAW_SYSTEM_PROMPT = """你是一名资深法务风控专家,精通以下法律法规:
|
||||
- 《生成式人工智能服务管理暂行办法》
|
||||
- 《互联网信息服务算法推荐管理规定》
|
||||
- 《数据安全法》《个人信息保护法》
|
||||
- 《出口管制法》及美国 BIS 实体清单相关规则
|
||||
- 《科创板上市规则》中的合规要求
|
||||
|
||||
你的任务是基于提供的企业数据,从法律合规角度评估以下风险:
|
||||
1. 算法备案合规风险:企业是否涉及AI业务但未完成算法备案
|
||||
2. 地缘政治与出口管制风险:企业或其供应链是否受到制裁
|
||||
3. 数据合规风险:是否存在数据出境、数据安全方面的隐患
|
||||
4. 知识产权诉讼风险:是否面临重大IP纠纷
|
||||
|
||||
请以严谨的法律视角进行评估,输出JSON格式:
|
||||
{
|
||||
"agent": "法务风控节点",
|
||||
"algo_compliance_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"geopolitical_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"data_compliance_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"ip_litigation_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"overall_law_risk": {"score": 0-100, "level": "高/中/低"},
|
||||
"key_findings": ["..."],
|
||||
"recommendations": ["..."]
|
||||
}"""
|
||||
|
||||
|
||||
class LawAgent(BaseAgent):
|
||||
"""法务风控节点"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name="法务风控节点",
|
||||
system_prompt=LAW_SYSTEM_PROMPT,
|
||||
role_icon="👩⚖️",
|
||||
)
|
||||
|
||||
def evaluate(self, company_data: dict) -> dict:
|
||||
"""执行法务风险评估"""
|
||||
prompt = self._build_prompt(company_data)
|
||||
result = self.infer_json(prompt)
|
||||
|
||||
# 如果 JSON 解析失败,使用规则引擎
|
||||
if result.get("parse_error"):
|
||||
result = self._rule_based_evaluation(company_data)
|
||||
|
||||
result["agent"] = "法务风控节点"
|
||||
result["icon"] = self.role_icon
|
||||
return result
|
||||
|
||||
def _build_prompt(self, company_data: dict) -> str:
|
||||
"""构建评估提示词"""
|
||||
return f"""请对以下科创企业进行法务风险评估:
|
||||
|
||||
企业名称:{company_data.get('short_name', company_data.get('company_name', '未知'))}
|
||||
行业:{company_data.get('industry', '未知')}
|
||||
领域:{company_data.get('sector', '未知')}
|
||||
|
||||
合规状态:
|
||||
- 算法备案:{json.dumps(company_data.get('compliance', {}), ensure_ascii=False)}
|
||||
|
||||
供应链信息:
|
||||
- 关键供应商:{json.dumps(company_data.get('supply_chain', {}).get('key_suppliers', []), ensure_ascii=False)}
|
||||
- 供应商集中度风险:{company_data.get('supply_chain', {}).get('supplier_concentration_risk', '未知')}
|
||||
|
||||
技术路线:
|
||||
{json.dumps(company_data.get('tech_route', {}), ensure_ascii=False)}
|
||||
|
||||
请输出严格的JSON评估结果。"""
|
||||
|
||||
def fallback_inference(self, prompt: str) -> str:
|
||||
"""规则引擎降级"""
|
||||
return json.dumps(self._rule_based_evaluation({}), ensure_ascii=False)
|
||||
|
||||
def _rule_based_evaluation(self, company_data: dict) -> dict:
|
||||
"""基于规则的法务风险评估"""
|
||||
compliance = company_data.get("compliance", {})
|
||||
supply_chain = company_data.get("supply_chain", {})
|
||||
sector = company_data.get("sector", "")
|
||||
|
||||
# 算法备案风险
|
||||
algo_status = compliance.get("algo_filing_status", "")
|
||||
algo_score = 20
|
||||
algo_detail = "合规状态正常"
|
||||
if sector in ["AI", "软件", "互联网"] and algo_status == "不适用":
|
||||
algo_score = 60
|
||||
algo_detail = "涉及AI业务但标注为不适用,建议核实"
|
||||
elif "未" in algo_status or not algo_status:
|
||||
algo_score = 80
|
||||
algo_detail = "未查到算法备案记录,存在合规风险"
|
||||
elif "已备案" in algo_status:
|
||||
algo_score = 10
|
||||
algo_detail = "已完成算法备案"
|
||||
|
||||
# 地缘政治风险
|
||||
entity_status = compliance.get("entity_list_status", "")
|
||||
geo_score = 15
|
||||
geo_detail = "未受出口管制影响"
|
||||
if "被列入" in entity_status:
|
||||
geo_score = 95
|
||||
geo_detail = f"已被列入实体清单: {compliance.get('sanctions_detail', '')}"
|
||||
elif supply_chain.get("supplier_concentration_risk") == "极高":
|
||||
geo_score = 70
|
||||
geo_detail = "核心供应链高度依赖海外,存在间接制裁风险"
|
||||
|
||||
# 数据合规风险
|
||||
data_risk = compliance.get("data_export_risk", "低")
|
||||
data_score = {"高": 75, "中": 45, "低": 15}.get(data_risk, 20)
|
||||
data_detail = f"数据出境风险等级: {data_risk}"
|
||||
|
||||
# 知识产权风险
|
||||
ip_score = 25
|
||||
ip_detail = "未发现重大IP纠纷"
|
||||
|
||||
# 综合法务风险
|
||||
overall_score = int(
|
||||
algo_score * 0.25 + geo_score * 0.35 +
|
||||
data_score * 0.25 + ip_score * 0.15
|
||||
)
|
||||
|
||||
return {
|
||||
"algo_compliance_risk": {"score": algo_score, "level": self._level(algo_score), "detail": algo_detail},
|
||||
"geopolitical_risk": {"score": geo_score, "level": self._level(geo_score), "detail": geo_detail},
|
||||
"data_compliance_risk": {"score": data_score, "level": self._level(data_score), "detail": data_detail},
|
||||
"ip_litigation_risk": {"score": ip_score, "level": self._level(ip_score), "detail": ip_detail},
|
||||
"overall_law_risk": {"score": overall_score, "level": self._level(overall_score)},
|
||||
"key_findings": [algo_detail, geo_detail, data_detail],
|
||||
"recommendations": ["建议定期审查合规状态", "关注实体清单更新动态"],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _level(score: int) -> str:
|
||||
if score >= 70:
|
||||
return "高"
|
||||
elif score >= 40:
|
||||
return "中"
|
||||
return "低"
|
||||
@@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
技术风控节点
|
||||
审查维度:技术路线竞争态势、核心人员稳定性、专利布局、技术替代风险
|
||||
"""
|
||||
import json
|
||||
from .base_agent import BaseAgent
|
||||
|
||||
|
||||
TECH_SYSTEM_PROMPT = """你是一名资深科技行业分析师和技术风控专家。
|
||||
你精通半导体、人工智能、新能源、生物医疗等前沿科技领域的技术演进趋势。
|
||||
|
||||
你的任务是基于提供的企业数据,从技术角度评估以下风险:
|
||||
1. 技术路线颠覆风险:企业押注的技术路线是否面临被替代的风险
|
||||
2. 核心人员流失风险:关键技术人员的稳定性和不可替代性
|
||||
3. 专利/技术壁垒:技术护城河的深度和可持续性
|
||||
4. 技术迭代压力:行业技术迭代速度对企业的冲击
|
||||
|
||||
请以技术专家的视角进行深度评估,输出JSON格式:
|
||||
{
|
||||
"agent": "技术风控节点",
|
||||
"tech_disruption_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"talent_loss_risk": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"patent_moat": {"score": 0-100, "level": "强/中/弱", "detail": "..."},
|
||||
"tech_iteration_pressure": {"score": 0-100, "level": "高/中/低", "detail": "..."},
|
||||
"overall_tech_risk": {"score": 0-100, "level": "高/中/低"},
|
||||
"key_findings": ["..."],
|
||||
"recommendations": ["..."]
|
||||
}"""
|
||||
|
||||
|
||||
class TechAgent(BaseAgent):
|
||||
"""技术风控节点"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name="技术风控节点",
|
||||
system_prompt=TECH_SYSTEM_PROMPT,
|
||||
role_icon="👨🔬",
|
||||
)
|
||||
|
||||
def evaluate(self, company_data: dict) -> dict:
|
||||
"""执行技术风险评估"""
|
||||
prompt = self._build_prompt(company_data)
|
||||
result = self.infer_json(prompt)
|
||||
|
||||
if result.get("parse_error"):
|
||||
result = self._rule_based_evaluation(company_data)
|
||||
|
||||
result["agent"] = "技术风控节点"
|
||||
result["icon"] = self.role_icon
|
||||
return result
|
||||
|
||||
def _build_prompt(self, company_data: dict) -> str:
|
||||
return f"""请对以下科创企业进行技术风险评估:
|
||||
|
||||
企业名称:{company_data.get('short_name', '未知')}
|
||||
行业:{company_data.get('industry', '未知')}
|
||||
领域:{company_data.get('sector', '未知')}
|
||||
企业描述:{company_data.get('description', '')}
|
||||
|
||||
技术路线信息:
|
||||
{json.dumps(company_data.get('tech_route', {}), ensure_ascii=False, indent=2)}
|
||||
|
||||
核心技术人员:
|
||||
{json.dumps(company_data.get('core_tech_personnel', []), ensure_ascii=False, indent=2)}
|
||||
|
||||
财务中的研发指标:
|
||||
- 研发费用: {company_data.get('financials', {}).get('rd_expense_2024', 0)}
|
||||
- 研发营收比: {company_data.get('financials', {}).get('rd_revenue_ratio', 0)}
|
||||
|
||||
请输出严格的JSON评估结果。"""
|
||||
|
||||
def _rule_based_evaluation(self, company_data: dict) -> dict:
|
||||
"""基于规则的技术风险评估"""
|
||||
tech_route = company_data.get("tech_route", {})
|
||||
personnel = company_data.get("core_tech_personnel", [])
|
||||
financials = company_data.get("financials", {})
|
||||
|
||||
# 技术路线颠覆风险
|
||||
competing_techs = tech_route.get("competing_techs", [])
|
||||
disruption_score = min(20 + len(competing_techs) * 15, 90)
|
||||
tech_moat = tech_route.get("tech_moat", "")
|
||||
if "差距" in tech_moat or "受制" in tech_moat:
|
||||
disruption_score = min(disruption_score + 20, 95)
|
||||
disruption_detail = f"面临 {len(competing_techs)} 条竞争技术路线: {', '.join(competing_techs[:3])}"
|
||||
|
||||
# 核心人员流失风险
|
||||
talent_score = 20
|
||||
talent_detail = "核心团队稳定"
|
||||
departed = [p for p in personnel if "离职" in p.get("status", "")]
|
||||
high_importance = [p for p in personnel if p.get("importance") == "极高"]
|
||||
|
||||
if departed:
|
||||
talent_score = 80
|
||||
talent_detail = f"已有核心人员离职: {', '.join(p['name'] for p in departed)}"
|
||||
elif len(high_importance) == 1:
|
||||
talent_score = 55
|
||||
talent_detail = f"高度依赖单一核心人员: {high_importance[0]['name']}"
|
||||
elif len(personnel) <= 2:
|
||||
talent_score = 45
|
||||
talent_detail = "核心技术团队规模偏小"
|
||||
|
||||
# 专利壁垒
|
||||
patent_count = tech_route.get("patent_count", 0)
|
||||
if patent_count > 5000:
|
||||
patent_score = 20
|
||||
patent_detail = f"专利数量充足({patent_count}件),技术壁垒较强"
|
||||
elif patent_count > 1000:
|
||||
patent_score = 35
|
||||
patent_detail = f"专利数量中等({patent_count}件)"
|
||||
else:
|
||||
patent_score = 60
|
||||
patent_detail = f"专利数量偏少({patent_count}件),技术壁垒偏弱"
|
||||
|
||||
# 技术迭代压力(基于研发投入比)
|
||||
rd_ratio = financials.get("rd_revenue_ratio", 0)
|
||||
if rd_ratio > 0.3:
|
||||
iter_score = 65
|
||||
iter_detail = f"研发营收比极高({rd_ratio:.1%}),说明行业技术迭代压力大"
|
||||
elif rd_ratio > 0.15:
|
||||
iter_score = 45
|
||||
iter_detail = f"研发投入较高({rd_ratio:.1%}),需持续技术投入"
|
||||
else:
|
||||
iter_score = 25
|
||||
iter_detail = f"研发投入适中({rd_ratio:.1%})"
|
||||
|
||||
overall = int(disruption_score * 0.35 + talent_score * 0.25 +
|
||||
patent_score * 0.15 + iter_score * 0.25)
|
||||
|
||||
return {
|
||||
"tech_disruption_risk": {"score": disruption_score, "level": self._level(disruption_score), "detail": disruption_detail},
|
||||
"talent_loss_risk": {"score": talent_score, "level": self._level(talent_score), "detail": talent_detail},
|
||||
"patent_moat": {"score": patent_score, "level": "弱" if patent_score >= 50 else ("中" if patent_score >= 30 else "强"), "detail": patent_detail},
|
||||
"tech_iteration_pressure": {"score": iter_score, "level": self._level(iter_score), "detail": iter_detail},
|
||||
"overall_tech_risk": {"score": overall, "level": self._level(overall)},
|
||||
"key_findings": [disruption_detail, talent_detail, patent_detail],
|
||||
"recommendations": ["关注竞争技术路线发展", "加强核心人员留任激励"],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _level(score: int) -> str:
|
||||
if score >= 70:
|
||||
return "高"
|
||||
elif score >= 40:
|
||||
return "中"
|
||||
return "低"
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""数据采集模块"""
|
||||
@@ -0,0 +1,188 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
年报 PDF 文本解析模块
|
||||
从年报中提取关键风险信息:核心技术人员、技术路线、诉讼、风险提示等
|
||||
"""
|
||||
import re
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_annual_report(pdf_path: str) -> dict:
|
||||
"""
|
||||
解析年报 PDF,提取关键风险相关信息
|
||||
返回结构化的风险要素字典
|
||||
"""
|
||||
text = _extract_text_from_pdf(pdf_path)
|
||||
if not text:
|
||||
return {"error": "PDF 解析失败", "raw_text": ""}
|
||||
|
||||
return {
|
||||
"core_personnel_info": _extract_core_personnel(text),
|
||||
"tech_route_info": _extract_tech_route(text),
|
||||
"litigation_info": _extract_litigation(text),
|
||||
"risk_factors": _extract_risk_factors(text),
|
||||
"rd_capitalization_info": _extract_rd_capitalization(text),
|
||||
"customer_concentration": _extract_customer_concentration(text),
|
||||
"raw_text_length": len(text),
|
||||
}
|
||||
|
||||
|
||||
def _extract_text_from_pdf(pdf_path: str) -> Optional[str]:
|
||||
"""使用 pdfplumber 提取 PDF 全文"""
|
||||
try:
|
||||
import pdfplumber
|
||||
text_parts = []
|
||||
with pdfplumber.open(pdf_path) as pdf:
|
||||
for page in pdf.pages:
|
||||
page_text = page.extract_text()
|
||||
if page_text:
|
||||
text_parts.append(page_text)
|
||||
return "\n".join(text_parts)
|
||||
except ImportError:
|
||||
logger.warning("pdfplumber 未安装,尝试 PyPDF2")
|
||||
try:
|
||||
from PyPDF2 import PdfReader
|
||||
reader = PdfReader(pdf_path)
|
||||
return "\n".join(
|
||||
page.extract_text() or "" for page in reader.pages
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"PyPDF2 解析失败: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"PDF 解析失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _extract_core_personnel(text: str) -> dict:
|
||||
"""提取核心技术人员相关信息"""
|
||||
result = {
|
||||
"has_departure": False,
|
||||
"departure_details": [],
|
||||
"personnel_count": 0,
|
||||
"key_mentions": [],
|
||||
}
|
||||
|
||||
# 匹配离职/辞职相关表述
|
||||
departure_patterns = [
|
||||
r"(核心技术人员|核心人员|关键技术人员).{0,30}(离职|辞职|离任|不再担任)",
|
||||
r"(CTO|首席技术官|技术总监|研发总监).{0,30}(离职|辞职|离任)",
|
||||
r"(离职|辞职).{0,30}(核心技术人员|核心人员)",
|
||||
]
|
||||
for pattern in departure_patterns:
|
||||
matches = re.findall(pattern, text)
|
||||
if matches:
|
||||
result["has_departure"] = True
|
||||
result["departure_details"].extend([str(m) for m in matches])
|
||||
|
||||
# 统计核心技术人员数量
|
||||
count_match = re.search(r"核心技术人员\s*(\d+)\s*[名人]", text)
|
||||
if count_match:
|
||||
result["personnel_count"] = int(count_match.group(1))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _extract_tech_route(text: str) -> dict:
|
||||
"""提取技术路线相关信息"""
|
||||
result = {
|
||||
"competing_tech_mentioned": False,
|
||||
"tech_keywords": [],
|
||||
"risk_mentions": [],
|
||||
}
|
||||
|
||||
# 技术竞争关键词
|
||||
tech_keywords = [
|
||||
"技术路线", "技术迭代", "技术替代", "技术颠覆",
|
||||
"竞争技术", "替代方案", "新一代技术",
|
||||
]
|
||||
for kw in tech_keywords:
|
||||
if kw in text:
|
||||
result["tech_keywords"].append(kw)
|
||||
result["competing_tech_mentioned"] = True
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _extract_litigation(text: str) -> dict:
|
||||
"""提取诉讼/仲裁相关信息"""
|
||||
result = {
|
||||
"has_litigation": False,
|
||||
"litigation_count": 0,
|
||||
"ip_related": False,
|
||||
}
|
||||
|
||||
# 诉讼关键词
|
||||
litigation_patterns = [
|
||||
r"(诉讼|仲裁|起诉|被告).{0,50}(知识产权|专利|商标|著作权)",
|
||||
r"(专利侵权|商标侵权|著作权纠纷)",
|
||||
]
|
||||
for pattern in litigation_patterns:
|
||||
if re.search(pattern, text):
|
||||
result["has_litigation"] = True
|
||||
result["ip_related"] = True
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _extract_risk_factors(text: str) -> list:
|
||||
"""提取风险因素章节的关键信息"""
|
||||
risk_keywords = [
|
||||
"地缘政治", "实体清单", "出口管制", "贸易摩擦",
|
||||
"数据安全", "数据合规", "算法备案", "数据出境",
|
||||
"客户集中", "供应商集中", "单一客户", "单一供应商",
|
||||
"研发资本化", "开发支出", "无形资产",
|
||||
"人才流失", "核心人员", "竞业限制",
|
||||
]
|
||||
found_risks = []
|
||||
for kw in risk_keywords:
|
||||
if kw in text:
|
||||
found_risks.append(kw)
|
||||
return found_risks
|
||||
|
||||
|
||||
def _extract_rd_capitalization(text: str) -> dict:
|
||||
"""提取研发资本化相关信息"""
|
||||
result = {
|
||||
"has_capitalization": False,
|
||||
"capitalization_mentioned": False,
|
||||
"amount_keywords": [],
|
||||
}
|
||||
|
||||
cap_keywords = ["开发支出", "研发资本化", "资本化研发", "开发阶段支出"]
|
||||
for kw in cap_keywords:
|
||||
if kw in text:
|
||||
result["capitalization_mentioned"] = True
|
||||
result["amount_keywords"].append(kw)
|
||||
|
||||
# 检查是否有具体的资本化金额
|
||||
cap_amount = re.search(r"开发支出.{0,30}([\d,\.]+)\s*(万元|百万|亿)", text)
|
||||
if cap_amount:
|
||||
result["has_capitalization"] = True
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _extract_customer_concentration(text: str) -> dict:
|
||||
"""提取客户/供应商集中度信息"""
|
||||
result = {
|
||||
"top5_customer_ratio": None,
|
||||
"top5_supplier_ratio": None,
|
||||
"single_customer_dependency": False,
|
||||
}
|
||||
|
||||
# 前五大客户占比
|
||||
customer_match = re.search(
|
||||
r"前五[名大]客户.{0,30}([\d\.]+)\s*%", text
|
||||
)
|
||||
if customer_match:
|
||||
result["top5_customer_ratio"] = float(customer_match.group(1)) / 100
|
||||
|
||||
# 单一客户依赖
|
||||
if re.search(r"(第一大客户|最大客户).{0,30}([\d\.]+)\s*%", text):
|
||||
result["single_customer_dependency"] = True
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
算法备案合规数据采集与查询模块
|
||||
匹配企业是否已完成网信办算法备案
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent / "data"
|
||||
|
||||
|
||||
def _load_algo_filings() -> list:
|
||||
"""加载算法备案数据"""
|
||||
filepath = DATA_DIR / "algo_filings.json"
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def check_algo_filing(company_name: str) -> dict:
|
||||
"""
|
||||
查询企业的算法备案状态
|
||||
"""
|
||||
filings = _load_algo_filings()
|
||||
result = {
|
||||
"has_filing": False,
|
||||
"filings": [],
|
||||
"needs_filing": False, # 是否需要备案但未备案
|
||||
"risk_level": "低",
|
||||
}
|
||||
|
||||
for filing in filings:
|
||||
if company_name in filing["company"] or filing["company"] in company_name:
|
||||
result["has_filing"] = True
|
||||
result["filings"].append(filing)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def assess_algo_compliance_risk(company_data: dict) -> dict:
|
||||
"""
|
||||
综合评估企业的算法合规风险
|
||||
考虑因素:是否涉及 AI 业务、是否已备案、数据出境风险
|
||||
"""
|
||||
company_name = company_data.get("short_name", company_data.get("company_name", ""))
|
||||
sector = company_data.get("sector", "")
|
||||
compliance = company_data.get("compliance", {})
|
||||
|
||||
# 查询备案状态
|
||||
filing_status = check_algo_filing(company_name)
|
||||
|
||||
# 判断是否需要备案
|
||||
ai_related_sectors = ["AI", "软件", "互联网", "消费电子"]
|
||||
needs_filing = sector in ai_related_sectors or "AI" in str(company_data.get("tech_route", {}))
|
||||
|
||||
# 综合评估
|
||||
risk_level = "低"
|
||||
risk_details = []
|
||||
|
||||
if needs_filing and not filing_status["has_filing"]:
|
||||
algo_status = compliance.get("algo_filing_status", "")
|
||||
if algo_status == "不适用":
|
||||
risk_level = "低"
|
||||
else:
|
||||
risk_level = "高"
|
||||
risk_details.append("涉及AI业务但未查到算法备案记录")
|
||||
|
||||
data_export_risk = compliance.get("data_export_risk", "低")
|
||||
if data_export_risk == "高":
|
||||
risk_level = "高"
|
||||
risk_details.append("存在大量跨境数据传输,数据出境评估风险高")
|
||||
elif data_export_risk == "中":
|
||||
if risk_level != "高":
|
||||
risk_level = "中"
|
||||
risk_details.append("存在部分跨境数据传输,需关注数据出境合规")
|
||||
|
||||
return {
|
||||
"company_name": company_name,
|
||||
"needs_filing": needs_filing,
|
||||
"filing_status": filing_status,
|
||||
"data_export_risk": data_export_risk,
|
||||
"overall_risk_level": risk_level,
|
||||
"risk_details": risk_details,
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
BIS 实体清单采集与匹配模块
|
||||
支持企业名模糊匹配 + 别名映射 + 供应链上游穿透
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent / "data"
|
||||
|
||||
|
||||
def _load_entity_list() -> list:
|
||||
"""加载实体清单数据"""
|
||||
filepath = DATA_DIR / "entity_list.json"
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def check_entity_list(company_name: str) -> dict:
|
||||
"""
|
||||
检查企业是否在 BIS 实体清单中
|
||||
支持模糊匹配和别名匹配
|
||||
"""
|
||||
entities = _load_entity_list()
|
||||
result = {
|
||||
"is_sanctioned": False,
|
||||
"match_type": None,
|
||||
"entity_detail": None,
|
||||
"supply_chain_risk": [], # 供应链上游被制裁的情况
|
||||
}
|
||||
|
||||
for entity in entities:
|
||||
# 精确匹配
|
||||
if company_name in entity["entity_name"]:
|
||||
result["is_sanctioned"] = True
|
||||
result["match_type"] = "直接命中"
|
||||
result["entity_detail"] = entity
|
||||
return result
|
||||
|
||||
# 别名匹配
|
||||
for alias in entity.get("aliases", []):
|
||||
if company_name in alias or alias in company_name:
|
||||
result["is_sanctioned"] = True
|
||||
result["match_type"] = "别名命中"
|
||||
result["entity_detail"] = entity
|
||||
return result
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def check_supply_chain_sanctions(company_name: str, suppliers: list) -> list:
|
||||
"""
|
||||
检查企业供应链上游是否有被制裁的实体
|
||||
返回受制裁的供应商列表
|
||||
"""
|
||||
sanctioned_suppliers = []
|
||||
entities = _load_entity_list()
|
||||
|
||||
for supplier in suppliers:
|
||||
# 清洗供应商名称(去掉括号中的说明文字)
|
||||
clean_name = supplier.split("(")[0].split("(")[0].strip()
|
||||
|
||||
for entity in entities:
|
||||
all_names = [entity["entity_name"]] + entity.get("aliases", [])
|
||||
for name in all_names:
|
||||
if clean_name in name or name in clean_name:
|
||||
sanctioned_suppliers.append({
|
||||
"supplier": supplier,
|
||||
"matched_entity": entity["entity_name"],
|
||||
"restrictions": entity["restrictions"],
|
||||
"date_added": entity["date_added"],
|
||||
})
|
||||
break
|
||||
|
||||
return sanctioned_suppliers
|
||||
|
||||
|
||||
def get_all_sanctioned_entities() -> list:
|
||||
"""获取所有被制裁实体列表"""
|
||||
return _load_entity_list()
|
||||
@@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
财务数据采集模块
|
||||
双轨策略:优先尝试 AKShare 在线采集,失败则回退到预置数据
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent / "data"
|
||||
|
||||
|
||||
def _load_preset_data() -> list:
|
||||
"""加载预置的科创板企业数据"""
|
||||
filepath = DATA_DIR / "sample_companies.json"
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def collect_financial_data(stock_code: str) -> Optional[dict]:
|
||||
"""
|
||||
采集指定股票代码的财务数据
|
||||
双轨策略:在线采集 → 离线预置
|
||||
"""
|
||||
# 尝试在线采集
|
||||
try:
|
||||
return _collect_online(stock_code)
|
||||
except Exception as e:
|
||||
logger.warning(f"在线采集 {stock_code} 失败: {e},回退到预置数据")
|
||||
|
||||
# 回退到预置数据
|
||||
return _collect_from_preset(stock_code)
|
||||
|
||||
|
||||
def _collect_online(stock_code: str) -> Optional[dict]:
|
||||
"""通过 AKShare 在线采集财务数据"""
|
||||
try:
|
||||
import akshare as ak
|
||||
|
||||
# 科创板企业利润表
|
||||
profit_df = ak.stock_profit_sheet_by_report_em(symbol=stock_code)
|
||||
# 科创板企业资产负债表
|
||||
balance_df = ak.stock_balance_sheet_by_report_em(symbol=stock_code)
|
||||
|
||||
if profit_df is not None and not profit_df.empty:
|
||||
latest = profit_df.iloc[0]
|
||||
return {
|
||||
"stock_code": stock_code,
|
||||
"revenue": float(latest.get("营业收入", 0)),
|
||||
"net_profit": float(latest.get("净利润", 0)),
|
||||
"rd_expense": float(latest.get("研发费用", 0)),
|
||||
"source": "akshare_online",
|
||||
}
|
||||
except ImportError:
|
||||
logger.warning("AKShare 未安装,跳过在线采集")
|
||||
except Exception as e:
|
||||
logger.warning(f"AKShare 采集异常: {e}")
|
||||
|
||||
raise RuntimeError("在线采集失败")
|
||||
|
||||
|
||||
def _collect_from_preset(stock_code: str) -> Optional[dict]:
|
||||
"""从预置数据中查找企业"""
|
||||
companies = _load_preset_data()
|
||||
for company in companies:
|
||||
if company["stock_code"] == stock_code:
|
||||
return {
|
||||
"stock_code": stock_code,
|
||||
"company_name": company["company_name"],
|
||||
"industry": company["industry"],
|
||||
"sector": company["sector"],
|
||||
"financials": company["financials"],
|
||||
"core_tech_personnel": company["core_tech_personnel"],
|
||||
"tech_route": company["tech_route"],
|
||||
"compliance": company["compliance"],
|
||||
"supply_chain": company["supply_chain"],
|
||||
"source": "preset_data",
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def get_all_companies() -> list:
|
||||
"""获取所有预置企业列表"""
|
||||
return _load_preset_data()
|
||||
|
||||
|
||||
def get_company_by_code(stock_code: str) -> Optional[dict]:
|
||||
"""通过股票代码查找企业完整数据"""
|
||||
companies = _load_preset_data()
|
||||
for company in companies:
|
||||
if company["stock_code"] == stock_code:
|
||||
return company
|
||||
return None
|
||||
|
||||
|
||||
def get_company_by_name(name: str) -> Optional[dict]:
|
||||
"""通过企业名称查找(支持简称)"""
|
||||
companies = _load_preset_data()
|
||||
for company in companies:
|
||||
if name in company["company_name"] or name in company["short_name"]:
|
||||
return company
|
||||
return None
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
全局配置文件
|
||||
集中管理 API Key、文件路径、模型参数等
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# ============================================================
|
||||
# 项目根目录
|
||||
# ============================================================
|
||||
PROJECT_ROOT = Path(__file__).parent.resolve()
|
||||
DATA_DIR = PROJECT_ROOT / "data"
|
||||
|
||||
# ============================================================
|
||||
# DeepSeek API 配置(兼容 OpenAI 接口协议)
|
||||
# ============================================================
|
||||
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
|
||||
DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1"
|
||||
DEEPSEEK_MODEL = "deepseek-chat"
|
||||
|
||||
# 备用1:火山引擎方舟(豆包大模型 doubao-seed-2-1-pro-260628)
|
||||
VOLCENGINE_API_KEY = os.environ.get("VOLCENGINE_API_KEY", os.environ.get("ARK_API_KEY", "836d9bc0-80e8-4e45-90df-7287994d91ec"))
|
||||
VOLCENGINE_BASE_URL = os.environ.get("VOLCENGINE_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3")
|
||||
VOLCENGINE_MODEL = os.environ.get("VOLCENGINE_MODEL", "doubao-seed-2-0-lite-260428")
|
||||
|
||||
# 备用2:如果用户配置了其他兼容 OpenAI 协议的 API
|
||||
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
|
||||
OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1")
|
||||
OPENAI_MODEL = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
|
||||
|
||||
# ============================================================
|
||||
# LLM 调用参数
|
||||
# ============================================================
|
||||
LLM_TEMPERATURE = 0.1 # 低温度保证输出稳定
|
||||
LLM_MAX_TOKENS = 4096
|
||||
LLM_TIMEOUT = 60 # 超时秒数
|
||||
LLM_MAX_RETRIES = 2 # 最大重试次数
|
||||
|
||||
# ============================================================
|
||||
# 风险评估权重配置
|
||||
# ============================================================
|
||||
RISK_WEIGHTS = {
|
||||
"tech_disruption": 0.20, # 技术路线颠覆风险
|
||||
"talent_loss": 0.15, # 核心人员流失风险
|
||||
"algo_compliance": 0.15, # 算法/数据合规风险
|
||||
"geopolitical": 0.20, # 地缘政治/出口管制风险
|
||||
"rd_capitalization": 0.15, # 研发资本化操纵风险
|
||||
"concentration": 0.15, # 客户/供应商集中风险
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 保险定价参数
|
||||
# ============================================================
|
||||
INSURANCE_PRODUCTS = {
|
||||
"ip_lawsuit": {
|
||||
"name": "知识产权被诉险",
|
||||
"base_premium": 50000, # 基础保费(元)
|
||||
"base_coverage": 5000000, # 基础保额(元)
|
||||
"description": "覆盖因知识产权纠纷(专利、商标、著作权)产生的诉讼费用及赔偿金",
|
||||
},
|
||||
"exec_departure": {
|
||||
"name": "高管离职业务中断险",
|
||||
"base_premium": 80000,
|
||||
"base_coverage": 10000000,
|
||||
"description": "覆盖核心技术人员/高管离职导致的业务中断损失",
|
||||
},
|
||||
"data_compliance": {
|
||||
"name": "数据合规行政处罚险",
|
||||
"base_premium": 30000,
|
||||
"base_coverage": 3000000,
|
||||
"description": "覆盖因非主观恶意的数据合规违规产生的行政罚款",
|
||||
},
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Streamlit 页面配置
|
||||
# ============================================================
|
||||
PAGE_TITLE = "🛡️ 科创企业智能风控与核保系统"
|
||||
PAGE_ICON = "🛡️"
|
||||
LAYOUT = "wide"
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{"filing_id": "AL-2023-001", "company": "金山办公", "algo_name": "WPS AI 写作助手", "filing_date": "2023-08-15", "status": "已通过", "algo_type": "生成合成类"},
|
||||
{"filing_id": "AL-2023-002", "company": "传音控股", "algo_name": "AI 相机美颜算法", "filing_date": "2023-06-20", "status": "已通过", "algo_type": "个性化推荐类"},
|
||||
{"filing_id": "AL-2023-003", "company": "海天瑞声", "algo_name": "智能标注平台算法", "filing_date": "2023-09-10", "status": "已通过", "algo_type": "生成合成类"},
|
||||
{"filing_id": "AL-2023-004", "company": "百度", "algo_name": "文心一言大模型", "filing_date": "2023-08-31", "status": "已通过", "algo_type": "生成合成类"},
|
||||
{"filing_id": "AL-2023-005", "company": "阿里巴巴", "algo_name": "通义千问大模型", "filing_date": "2023-09-13", "status": "已通过", "algo_type": "生成合成类"},
|
||||
{"filing_id": "AL-2023-006", "company": "腾讯", "algo_name": "混元大模型", "filing_date": "2023-09-15", "status": "已通过", "algo_type": "生成合成类"},
|
||||
{"filing_id": "AL-2024-001", "company": "字节跳动", "algo_name": "豆包大模型", "filing_date": "2024-01-15", "status": "已通过", "algo_type": "生成合成类"},
|
||||
{"filing_id": "AL-2024-002", "company": "商汤科技", "algo_name": "日日新大模型", "filing_date": "2024-02-20", "status": "已通过", "algo_type": "生成合成类"},
|
||||
{"filing_id": "AL-2024-003", "company": "科大讯飞", "algo_name": "星火认知大模型", "filing_date": "2024-03-01", "status": "已通过", "algo_type": "生成合成类"}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{"entity_name": "中芯国际集成电路制造有限公司", "aliases": ["SMIC", "中芯国际", "Semiconductor Manufacturing International Corporation"], "date_added": "2020-12-18", "reason": "军事最终用途", "restrictions": "限制EUV光刻设备及先进制程相关技术出口", "source": "BIS Entity List"},
|
||||
{"entity_name": "寒武纪科技股份有限公司", "aliases": ["Cambricon", "寒武纪"], "date_added": "2022-10-07", "reason": "支持中国军事现代化", "restrictions": "限制先进AI芯片代工与设计软件", "source": "BIS Entity List"},
|
||||
{"entity_name": "华为技术有限公司", "aliases": ["Huawei", "华为", "HUAWEI"], "date_added": "2019-05-16", "reason": "国家安全威胁", "restrictions": "全面限制美国技术出口", "source": "BIS Entity List"},
|
||||
{"entity_name": "海康威视数字技术股份有限公司", "aliases": ["Hikvision", "海康威视"], "date_added": "2019-10-07", "reason": "参与新疆人权侵犯", "restrictions": "限制安防监控技术出口", "source": "BIS Entity List"},
|
||||
{"entity_name": "大疆创新科技有限公司", "aliases": ["DJI", "大疆"], "date_added": "2020-12-18", "reason": "军事最终用途", "restrictions": "限制无人机核心部件出口", "source": "BIS Entity List"},
|
||||
{"entity_name": "科大讯飞股份有限公司", "aliases": ["iFlytek", "科大讯飞"], "date_added": "2019-10-07", "reason": "参与新疆人权侵犯", "restrictions": "限制AI语音技术相关出口", "source": "BIS Entity List"},
|
||||
{"entity_name": "商汤科技有限公司", "aliases": ["SenseTime", "商汤", "商汤科技"], "date_added": "2021-12-10", "reason": "参与监控技术开发", "restrictions": "限制AI视觉技术出口", "source": "BIS Entity List / OFAC SDN List"},
|
||||
{"entity_name": "旷视科技有限公司", "aliases": ["Megvii", "旷视"], "date_added": "2019-10-07", "reason": "参与新疆人权侵犯", "restrictions": "限制AI人脸识别技术出口", "source": "BIS Entity List"},
|
||||
{"entity_name": "长江存储科技有限责任公司", "aliases": ["YMTC", "长江存储"], "date_added": "2022-12-15", "reason": "支持中国军事现代化", "restrictions": "限制NAND闪存芯片相关设备出口", "source": "BIS Entity List"},
|
||||
{"entity_name": "上海微电子装备集团股份有限公司", "aliases": ["SMEE", "上海微电子"], "date_added": "2022-10-07", "reason": "支持中国军事现代化", "restrictions": "限制光刻机核心部件", "source": "BIS Entity List"},
|
||||
{"entity_name": "龙芯中科技术股份有限公司", "aliases": ["Loongson", "龙芯"], "date_added": "2023-02-10", "reason": "军事最终用途", "restrictions": "限制先进CPU设计工具出口", "source": "BIS Entity List"},
|
||||
{"entity_name": "壁仞科技股份有限公司", "aliases": ["Biren Technology", "壁仞科技"], "date_added": "2022-10-07", "reason": "支持中国军事现代化", "restrictions": "限制GPU芯片代工", "source": "BIS Entity List"},
|
||||
{"entity_name": "摩尔线程智能科技股份有限公司", "aliases": ["Moore Threads", "摩尔线程"], "date_added": "2023-10-17", "reason": "支持中国军事现代化", "restrictions": "限制GPU芯片代工与设计", "source": "BIS Entity List"}
|
||||
]
|
||||
@@ -0,0 +1,408 @@
|
||||
[
|
||||
{
|
||||
"stock_code": "688981",
|
||||
"company_name": "中芯国际",
|
||||
"short_name": "中芯国际",
|
||||
"industry": "半导体制造",
|
||||
"sector": "芯片",
|
||||
"listed_board": "科创板",
|
||||
"description": "国内领先的集成电路晶圆代工企业,提供0.35微米到FinFET先进工艺",
|
||||
"core_tech_personnel": [
|
||||
{"name": "梁孟松", "title": "联合CEO/技术研发负责人", "status": "在职", "importance": "极高"},
|
||||
{"name": "周子学", "title": "董事长", "status": "在职", "importance": "高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 57756000000,
|
||||
"net_profit_2024": 3433000000,
|
||||
"rd_expense_2024": 5124000000,
|
||||
"rd_capitalization_rate": 0.0,
|
||||
"rd_revenue_ratio": 0.089,
|
||||
"top5_customer_ratio": 0.42,
|
||||
"top5_supplier_ratio": 0.55,
|
||||
"receivable_turnover": 8.2,
|
||||
"cash_flow_ratio": 1.35
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "14nm FinFET 量产, 7nm 研发中",
|
||||
"competing_techs": ["EUV 光刻技术 (ASML 垄断)", "GAA 晶体管架构"],
|
||||
"tech_moat": "国产替代核心标的,但受制于设备禁运",
|
||||
"patent_count": 12000
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "不适用",
|
||||
"data_export_risk": "低",
|
||||
"entity_list_status": "被列入(2020年12月)",
|
||||
"sanctions_detail": "被美国商务部列入实体清单,限制EUV光刻设备进口"
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["ASML(光刻机)", "东京电子(刻蚀设备)", "应用材料(薄膜沉积)"],
|
||||
"key_customers": ["高通", "联发科", "华为海思"],
|
||||
"supplier_concentration_risk": "极高——核心设备依赖海外供应商"
|
||||
}
|
||||
},
|
||||
{
|
||||
"stock_code": "688111",
|
||||
"company_name": "金山办公软件股份有限公司",
|
||||
"short_name": "金山办公",
|
||||
"industry": "办公软件",
|
||||
"sector": "软件",
|
||||
"listed_board": "科创板",
|
||||
"description": "国产办公软件龙头,WPS Office 全球月活超5.9亿",
|
||||
"core_tech_personnel": [
|
||||
{"name": "章庆元", "title": "CEO", "status": "在职", "importance": "极高"},
|
||||
{"name": "姚冬", "title": "CTO/AI研发负责人", "status": "在职", "importance": "高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 4586000000,
|
||||
"net_profit_2024": 1231000000,
|
||||
"rd_expense_2024": 1520000000,
|
||||
"rd_capitalization_rate": 0.0,
|
||||
"rd_revenue_ratio": 0.331,
|
||||
"top5_customer_ratio": 0.15,
|
||||
"top5_supplier_ratio": 0.30,
|
||||
"receivable_turnover": 12.5,
|
||||
"cash_flow_ratio": 1.82
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "WPS AI 大模型集成、云文档协同",
|
||||
"competing_techs": ["Microsoft 365 Copilot", "Google Workspace Gemini"],
|
||||
"tech_moat": "信创替代核心标的,政府及国企客户粘性高",
|
||||
"patent_count": 3500
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "已备案(WPS AI 写作助手)",
|
||||
"data_export_risk": "中(海外版WPS涉及数据跨境)",
|
||||
"entity_list_status": "未列入",
|
||||
"sanctions_detail": ""
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["华为云", "阿里云", "英伟达(GPU)"],
|
||||
"key_customers": ["各级政府机关", "央企国企", "中小企业"],
|
||||
"supplier_concentration_risk": "中——GPU算力依赖进口"
|
||||
}
|
||||
},
|
||||
{
|
||||
"stock_code": "688139",
|
||||
"company_name": "海尔生物医疗股份有限公司",
|
||||
"short_name": "海尔生物",
|
||||
"industry": "生物医疗低温存储",
|
||||
"sector": "医疗器械",
|
||||
"listed_board": "科创板",
|
||||
"description": "全球领先的生物医疗低温存储解决方案提供商",
|
||||
"core_tech_personnel": [
|
||||
{"name": "刘占杰", "title": "董事长/总经理", "status": "在职", "importance": "极高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 2245000000,
|
||||
"net_profit_2024": 398000000,
|
||||
"rd_expense_2024": 289000000,
|
||||
"rd_capitalization_rate": 0.05,
|
||||
"rd_revenue_ratio": 0.129,
|
||||
"top5_customer_ratio": 0.22,
|
||||
"top5_supplier_ratio": 0.35,
|
||||
"receivable_turnover": 6.8,
|
||||
"cash_flow_ratio": 1.15
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "超低温冰箱(-196°C)、自动化样本库、物联网疫苗管理",
|
||||
"competing_techs": ["赛默飞世尔", "松下医疗"],
|
||||
"tech_moat": "国内市场份额第一,物联网技术差异化",
|
||||
"patent_count": 800
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "不适用",
|
||||
"data_export_risk": "低",
|
||||
"entity_list_status": "未列入",
|
||||
"sanctions_detail": ""
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["压缩机供应商", "电子元器件供应商"],
|
||||
"key_customers": ["各级疾控中心", "医院", "科研院所"],
|
||||
"supplier_concentration_risk": "低"
|
||||
}
|
||||
},
|
||||
{
|
||||
"stock_code": "688256",
|
||||
"company_name": "寒武纪科技股份有限公司",
|
||||
"short_name": "寒武纪",
|
||||
"industry": "AI芯片",
|
||||
"sector": "芯片",
|
||||
"listed_board": "科创板",
|
||||
"description": "国内AI芯片独角兽,智能计算处理器IP及芯片产品提供商",
|
||||
"core_tech_personnel": [
|
||||
{"name": "陈天石", "title": "创始人/董事长/CEO", "status": "在职", "importance": "极高"},
|
||||
{"name": "陈云霁", "title": "首席科学家(兄长)", "status": "中科院任职,兼职顾问", "importance": "高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 1170000000,
|
||||
"net_profit_2024": -825000000,
|
||||
"rd_expense_2024": 1510000000,
|
||||
"rd_capitalization_rate": 0.0,
|
||||
"rd_revenue_ratio": 1.29,
|
||||
"top5_customer_ratio": 0.85,
|
||||
"top5_supplier_ratio": 0.70,
|
||||
"receivable_turnover": 3.2,
|
||||
"cash_flow_ratio": 0.45
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "思元系列AI推理/训练芯片, MLU架构",
|
||||
"competing_techs": ["英伟达CUDA生态", "华为昇腾", "AMD Instinct"],
|
||||
"tech_moat": "自主IP指令集,但生态建设与英伟达差距大",
|
||||
"patent_count": 2800
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "不适用(硬件厂商)",
|
||||
"data_export_risk": "低",
|
||||
"entity_list_status": "被列入(2022年10月)",
|
||||
"sanctions_detail": "被美国商务部列入实体清单,限制先进制程芯片代工"
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["台积电(已受限)", "中芯国际(替代)", "日月光(封测)"],
|
||||
"key_customers": ["中国移动", "南京市政府智慧城市项目", "某互联网大厂"],
|
||||
"supplier_concentration_risk": "极高——先进制程代工受限"
|
||||
}
|
||||
},
|
||||
{
|
||||
"stock_code": "688029",
|
||||
"company_name": "南微医学科技股份有限公司",
|
||||
"short_name": "南微医学",
|
||||
"industry": "微创医疗器械",
|
||||
"sector": "医疗器械",
|
||||
"listed_board": "科创板",
|
||||
"description": "全球领先的微创诊疗器械企业,内镜下诊疗器械龙头",
|
||||
"core_tech_personnel": [
|
||||
{"name": "隆晓辉", "title": "创始人/董事长", "status": "在职", "importance": "极高"},
|
||||
{"name": "冷德嵘", "title": "总经理", "status": "在职", "importance": "高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 2580000000,
|
||||
"net_profit_2024": 628000000,
|
||||
"rd_expense_2024": 365000000,
|
||||
"rd_capitalization_rate": 0.12,
|
||||
"rd_revenue_ratio": 0.141,
|
||||
"top5_customer_ratio": 0.18,
|
||||
"top5_supplier_ratio": 0.32,
|
||||
"receivable_turnover": 7.5,
|
||||
"cash_flow_ratio": 1.42
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "一次性内镜、电外科手术器械、AI辅助诊断",
|
||||
"competing_techs": ["波士顿科学", "奥林巴斯", "库克医疗"],
|
||||
"tech_moat": "国产微创器械龙头,海外收入占比超40%",
|
||||
"patent_count": 1200
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "不适用",
|
||||
"data_export_risk": "低",
|
||||
"entity_list_status": "未列入",
|
||||
"sanctions_detail": ""
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["不锈钢/钛合金供应商", "精密注塑件供应商"],
|
||||
"key_customers": ["全球三甲医院", "海外经销商网络"],
|
||||
"supplier_concentration_risk": "低"
|
||||
}
|
||||
},
|
||||
{
|
||||
"stock_code": "688126",
|
||||
"company_name": "沪硅产业集团股份有限公司",
|
||||
"short_name": "沪硅产业",
|
||||
"industry": "半导体硅片",
|
||||
"sector": "芯片",
|
||||
"listed_board": "科创板",
|
||||
"description": "国内规模最大的半导体硅片制造企业,300mm大硅片龙头",
|
||||
"core_tech_personnel": [
|
||||
{"name": "林林", "title": "总裁", "status": "在职", "importance": "极高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 3285000000,
|
||||
"net_profit_2024": -156000000,
|
||||
"rd_expense_2024": 498000000,
|
||||
"rd_capitalization_rate": 0.35,
|
||||
"rd_revenue_ratio": 0.152,
|
||||
"top5_customer_ratio": 0.62,
|
||||
"top5_supplier_ratio": 0.48,
|
||||
"receivable_turnover": 5.1,
|
||||
"cash_flow_ratio": 0.78
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "300mm半导体硅片(12英寸)量产",
|
||||
"competing_techs": ["日本信越化学", "日本SUMCO", "韩国SK Siltron"],
|
||||
"tech_moat": "国产替代第一梯队,但高端产品仍有差距",
|
||||
"patent_count": 650
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "不适用",
|
||||
"data_export_risk": "低",
|
||||
"entity_list_status": "未列入",
|
||||
"sanctions_detail": ""
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["多晶硅供应商", "石英坩埚供应商"],
|
||||
"key_customers": ["中芯国际", "华虹半导体", "长江存储"],
|
||||
"supplier_concentration_risk": "中"
|
||||
}
|
||||
},
|
||||
{
|
||||
"stock_code": "688223",
|
||||
"company_name": "晶科能源股份有限公司",
|
||||
"short_name": "晶科能源",
|
||||
"industry": "光伏组件",
|
||||
"sector": "新能源",
|
||||
"listed_board": "科创板",
|
||||
"description": "全球领先的光伏组件制造商,N型TOPCon技术全球领先",
|
||||
"core_tech_personnel": [
|
||||
{"name": "李仙德", "title": "创始人/董事长", "status": "在职", "importance": "极高"},
|
||||
{"name": "金浩", "title": "首席科学家", "status": "在职", "importance": "高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 84300000000,
|
||||
"net_profit_2024": 1050000000,
|
||||
"rd_expense_2024": 4890000000,
|
||||
"rd_capitalization_rate": 0.08,
|
||||
"rd_revenue_ratio": 0.058,
|
||||
"top5_customer_ratio": 0.25,
|
||||
"top5_supplier_ratio": 0.40,
|
||||
"receivable_turnover": 6.2,
|
||||
"cash_flow_ratio": 0.92
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "N型TOPCon高效电池, 钙钛矿叠层研发",
|
||||
"competing_techs": ["隆基HJT路线", "钙钛矿技术", "IBC电池"],
|
||||
"tech_moat": "TOPCon量产规模全球第一,但面临技术路线之争",
|
||||
"patent_count": 2200
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "不适用",
|
||||
"data_export_risk": "低",
|
||||
"entity_list_status": "未列入(但面临美国反倾销关税)",
|
||||
"sanctions_detail": "面临美国、欧盟反倾销/反补贴调查"
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["硅料供应商(通威/大全)", "银浆供应商", "EVA胶膜供应商"],
|
||||
"key_customers": ["全球EPC总包商", "欧美分布式市场", "中东/非洲新兴市场"],
|
||||
"supplier_concentration_risk": "中——硅料价格波动大"
|
||||
}
|
||||
},
|
||||
{
|
||||
"stock_code": "688036",
|
||||
"company_name": "传音控股股份有限公司",
|
||||
"short_name": "传音控股",
|
||||
"industry": "智能终端",
|
||||
"sector": "消费电子",
|
||||
"listed_board": "科创板",
|
||||
"description": "非洲市场手机销量第一,旗下TECNO/Infinix/itel品牌",
|
||||
"core_tech_personnel": [
|
||||
{"name": "竺兆江", "title": "创始人/董事长", "status": "在职", "importance": "极高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 65800000000,
|
||||
"net_profit_2024": 5620000000,
|
||||
"rd_expense_2024": 3250000000,
|
||||
"rd_capitalization_rate": 0.0,
|
||||
"rd_revenue_ratio": 0.049,
|
||||
"top5_customer_ratio": 0.32,
|
||||
"top5_supplier_ratio": 0.58,
|
||||
"receivable_turnover": 9.8,
|
||||
"cash_flow_ratio": 1.25
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "深肤色相机算法, AI翻译, 本地化OS",
|
||||
"competing_techs": ["三星", "小米", "OPPO"],
|
||||
"tech_moat": "新兴市场本地化经验深厚",
|
||||
"patent_count": 4500
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "已备案(AI相机美颜算法)",
|
||||
"data_export_risk": "高(大量非洲/东南亚用户数据跨境)",
|
||||
"entity_list_status": "未列入",
|
||||
"sanctions_detail": ""
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["联发科(芯片)", "三星SDI/ATL(电池)", "京东方(屏幕)"],
|
||||
"key_customers": ["非洲运营商", "东南亚分销商", "拉美零售商"],
|
||||
"supplier_concentration_risk": "中——芯片依赖联发科"
|
||||
}
|
||||
},
|
||||
{
|
||||
"stock_code": "688005",
|
||||
"company_name": "容百科技股份有限公司",
|
||||
"short_name": "容百科技",
|
||||
"industry": "锂电正极材料",
|
||||
"sector": "新能源",
|
||||
"listed_board": "科创板",
|
||||
"description": "全球领先的锂电池正极材料供应商,高镍三元材料龙头",
|
||||
"core_tech_personnel": [
|
||||
{"name": "白厚善", "title": "创始人/董事长", "status": "在职", "importance": "极高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 18900000000,
|
||||
"net_profit_2024": 356000000,
|
||||
"rd_expense_2024": 785000000,
|
||||
"rd_capitalization_rate": 0.18,
|
||||
"rd_revenue_ratio": 0.042,
|
||||
"top5_customer_ratio": 0.78,
|
||||
"top5_supplier_ratio": 0.65,
|
||||
"receivable_turnover": 4.5,
|
||||
"cash_flow_ratio": 0.68
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "超高镍三元正极材料(Ni>90%), 固态电池正极",
|
||||
"competing_techs": ["磷酸铁锂(比亚迪路线)", "钠离子电池", "固态电解质"],
|
||||
"tech_moat": "高镍三元出货量全球前三",
|
||||
"patent_count": 450
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "不适用",
|
||||
"data_export_risk": "低",
|
||||
"entity_list_status": "未列入",
|
||||
"sanctions_detail": ""
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["格林美(镍钴原料)", "华友钴业"],
|
||||
"key_customers": ["宁德时代", "三星SDI", "SK On"],
|
||||
"supplier_concentration_risk": "高——原材料价格波动大"
|
||||
}
|
||||
},
|
||||
{
|
||||
"stock_code": "688787",
|
||||
"company_name": "海天瑞声科技股份有限公司",
|
||||
"short_name": "海天瑞声",
|
||||
"industry": "AI训练数据",
|
||||
"sector": "AI",
|
||||
"listed_board": "科创板",
|
||||
"description": "国内领先的AI训练数据解决方案提供商",
|
||||
"core_tech_personnel": [
|
||||
{"name": "贺琳", "title": "创始人/董事长", "status": "在职", "importance": "极高"},
|
||||
{"name": "郭蕾", "title": "副总经理/技术负责人", "status": "离职(2024年8月)", "importance": "高"}
|
||||
],
|
||||
"financials": {
|
||||
"revenue_2024": 356000000,
|
||||
"net_profit_2024": -48000000,
|
||||
"rd_expense_2024": 102000000,
|
||||
"rd_capitalization_rate": 0.42,
|
||||
"rd_revenue_ratio": 0.287,
|
||||
"top5_customer_ratio": 0.88,
|
||||
"top5_supplier_ratio": 0.35,
|
||||
"receivable_turnover": 2.8,
|
||||
"cash_flow_ratio": 0.35
|
||||
},
|
||||
"tech_route": {
|
||||
"current_tech": "多语种语音数据集, 自动驾驶标注数据, AIGC合成数据",
|
||||
"competing_techs": ["Scale AI", "Appen", "AI合成数据替代人工标注"],
|
||||
"tech_moat": "多语种覆盖广泛,但面临AIGC合成数据的颠覆性威胁",
|
||||
"patent_count": 180
|
||||
},
|
||||
"compliance": {
|
||||
"algo_filing_status": "已备案(智能标注平台算法)",
|
||||
"data_export_risk": "高(大量跨境数据采集与交付)",
|
||||
"entity_list_status": "未列入",
|
||||
"sanctions_detail": ""
|
||||
},
|
||||
"supply_chain": {
|
||||
"key_suppliers": ["全球数据采集众包网络"],
|
||||
"key_customers": ["字节跳动", "百度", "某大型车企"],
|
||||
"supplier_concentration_risk": "中"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"supply_relations": [
|
||||
{"from": "ASML", "to": "中芯国际", "relation": "供应光刻设备", "critical": true, "status": "受限"},
|
||||
{"from": "东京电子", "to": "中芯国际", "relation": "供应刻蚀设备", "critical": true, "status": "受限"},
|
||||
{"from": "应用材料", "to": "中芯国际", "relation": "供应薄膜沉积设备", "critical": true, "status": "受限"},
|
||||
{"from": "中芯国际", "to": "寒武纪", "relation": "芯片代工", "critical": true, "status": "正常"},
|
||||
{"from": "中芯国际", "to": "华为海思", "relation": "芯片代工", "critical": true, "status": "受限"},
|
||||
{"from": "台积电", "to": "寒武纪", "relation": "先进制程代工", "critical": true, "status": "受限"},
|
||||
{"from": "英伟达", "to": "金山办公", "relation": "供应GPU算力", "critical": true, "status": "受限"},
|
||||
{"from": "英伟达", "to": "海天瑞声", "relation": "供应GPU算力(客户需求)", "critical": false, "status": "正常"},
|
||||
{"from": "联发科", "to": "传音控股", "relation": "供应手机芯片", "critical": true, "status": "正常"},
|
||||
{"from": "三星SDI", "to": "传音控股", "relation": "供应电池", "critical": false, "status": "正常"},
|
||||
{"from": "京东方", "to": "传音控股", "relation": "供应屏幕", "critical": false, "status": "正常"},
|
||||
{"from": "沪硅产业", "to": "中芯国际", "relation": "供应半导体硅片", "critical": true, "status": "正常"},
|
||||
{"from": "沪硅产业", "to": "华虹半导体", "relation": "供应半导体硅片", "critical": true, "status": "正常"},
|
||||
{"from": "沪硅产业", "to": "长江存储", "relation": "供应半导体硅片", "critical": true, "status": "正常"},
|
||||
{"from": "格林美", "to": "容百科技", "relation": "供应镍钴原料", "critical": true, "status": "正常"},
|
||||
{"from": "华友钴业", "to": "容百科技", "relation": "供应钴原料", "critical": true, "status": "正常"},
|
||||
{"from": "容百科技", "to": "宁德时代", "relation": "供应正极材料", "critical": true, "status": "正常"},
|
||||
{"from": "容百科技", "to": "三星SDI", "relation": "供应正极材料", "critical": false, "status": "正常"},
|
||||
{"from": "通威股份", "to": "晶科能源", "relation": "供应硅料", "critical": true, "status": "正常"},
|
||||
{"from": "大全能源", "to": "晶科能源", "relation": "供应硅料", "critical": true, "status": "正常"},
|
||||
{"from": "海天瑞声", "to": "字节跳动", "relation": "供应AI训练数据", "critical": false, "status": "正常"},
|
||||
{"from": "海天瑞声", "to": "百度", "relation": "供应AI训练数据", "critical": false, "status": "正常"}
|
||||
],
|
||||
"investment_relations": [
|
||||
{"from": "国家集成电路产业基金", "to": "中芯国际", "relation": "战略投资", "share_ratio": 0.15},
|
||||
{"from": "国家集成电路产业基金", "to": "沪硅产业", "relation": "战略投资", "share_ratio": 0.08},
|
||||
{"from": "国家集成电路产业基金", "to": "长江存储", "relation": "战略投资", "share_ratio": 0.20}
|
||||
],
|
||||
"personnel_relations": [
|
||||
{"person": "梁孟松", "from_company": "台积电/三星", "to_company": "中芯国际", "relation": "技术负责人跳槽", "year": 2017},
|
||||
{"person": "郭蕾", "from_company": "海天瑞声", "to_company": "未知", "relation": "核心技术人员离职", "year": 2024}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""知识图谱模块"""
|
||||
@@ -0,0 +1,169 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
供应链风险传染分析器
|
||||
基于 BFS 遍历实现风险穿透,计算传染距离和影响权重
|
||||
"""
|
||||
import logging
|
||||
from collections import deque
|
||||
from typing import Optional
|
||||
|
||||
import networkx as nx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def analyze_contagion(G: nx.DiGraph, target_company: str) -> dict:
|
||||
"""
|
||||
分析指定企业的供应链风险传染情况
|
||||
从上游(供应商)方向进行 BFS 穿透
|
||||
"""
|
||||
if target_company not in G:
|
||||
return {"error": f"企业 '{target_company}' 不在图谱中"}
|
||||
|
||||
result = {
|
||||
"company": target_company,
|
||||
"direct_risks": [], # 直接风险(一度关联)
|
||||
"indirect_risks": [], # 间接风险(二度及以上关联)
|
||||
"contagion_paths": [], # 风险传染路径
|
||||
"risk_score": 0, # 供应链风险总分
|
||||
"critical_nodes": [], # 关键断裂节点
|
||||
}
|
||||
|
||||
# BFS 从目标企业向上游遍历
|
||||
visited = set()
|
||||
queue = deque([(target_company, 0, [target_company])])
|
||||
visited.add(target_company)
|
||||
|
||||
while queue:
|
||||
current, depth, path = queue.popleft()
|
||||
|
||||
# 检查当前节点的上游(前驱节点)
|
||||
for predecessor in G.predecessors(current):
|
||||
if predecessor in visited:
|
||||
continue
|
||||
visited.add(predecessor)
|
||||
|
||||
edge_data = G.edges[predecessor, current]
|
||||
node_data = G.nodes.get(predecessor, {})
|
||||
new_path = [predecessor] + path
|
||||
|
||||
# 检查上游节点是否受制裁
|
||||
if node_data.get("is_sanctioned", False):
|
||||
risk_entry = {
|
||||
"entity": predecessor,
|
||||
"node_type": node_data.get("node_type", "未知"),
|
||||
"distance": depth + 1,
|
||||
"relation": edge_data.get("relation", ""),
|
||||
"is_critical": edge_data.get("critical", False),
|
||||
"status": edge_data.get("status", "正常"),
|
||||
"path": " → ".join(new_path),
|
||||
}
|
||||
|
||||
if depth == 0:
|
||||
result["direct_risks"].append(risk_entry)
|
||||
else:
|
||||
result["indirect_risks"].append(risk_entry)
|
||||
|
||||
result["contagion_paths"].append({
|
||||
"path": new_path,
|
||||
"path_str": " → ".join(new_path),
|
||||
"length": len(new_path),
|
||||
"severity": "高" if edge_data.get("critical", False) else "中",
|
||||
})
|
||||
|
||||
# 检查受限状态的边
|
||||
if edge_data.get("status") == "受限":
|
||||
if predecessor not in [r["entity"] for r in result["direct_risks"] + result["indirect_risks"]]:
|
||||
risk_entry = {
|
||||
"entity": predecessor,
|
||||
"node_type": node_data.get("node_type", "未知"),
|
||||
"distance": depth + 1,
|
||||
"relation": edge_data.get("relation", ""),
|
||||
"is_critical": edge_data.get("critical", False),
|
||||
"status": "受限",
|
||||
"path": " → ".join(new_path),
|
||||
}
|
||||
if depth == 0:
|
||||
result["direct_risks"].append(risk_entry)
|
||||
else:
|
||||
result["indirect_risks"].append(risk_entry)
|
||||
|
||||
# 继续向上游遍历(最多3层)
|
||||
if depth < 2:
|
||||
queue.append((predecessor, depth + 1, new_path))
|
||||
|
||||
# 计算供应链风险得分
|
||||
result["risk_score"] = _calculate_supply_chain_risk_score(result)
|
||||
|
||||
# 识别关键断裂节点
|
||||
result["critical_nodes"] = _find_critical_nodes(G, target_company)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _calculate_supply_chain_risk_score(contagion_result: dict) -> float:
|
||||
"""计算供应链风险综合得分 (0-100)"""
|
||||
score = 0
|
||||
|
||||
# 直接风险:每个 +25 分,关键供应 +35 分
|
||||
for risk in contagion_result["direct_risks"]:
|
||||
if risk["is_critical"]:
|
||||
score += 35
|
||||
else:
|
||||
score += 25
|
||||
|
||||
# 间接风险:每个 +10 分,关键供应 +15 分
|
||||
for risk in contagion_result["indirect_risks"]:
|
||||
if risk["is_critical"]:
|
||||
score += 15
|
||||
else:
|
||||
score += 10
|
||||
|
||||
return min(score, 100) # 上限 100
|
||||
|
||||
|
||||
def _find_critical_nodes(G: nx.DiGraph, target: str) -> list:
|
||||
"""
|
||||
识别关键断裂节点:如果移除该节点,目标企业将失去关键供应来源
|
||||
"""
|
||||
critical = []
|
||||
predecessors = list(G.predecessors(target))
|
||||
|
||||
for pred in predecessors:
|
||||
edge_data = G.edges[pred, target]
|
||||
if edge_data.get("critical", False):
|
||||
# 检查是否有替代供应商
|
||||
alternatives = sum(
|
||||
1 for p in predecessors
|
||||
if p != pred and G.edges[p, target].get("relation", "") == edge_data.get("relation", "")
|
||||
)
|
||||
critical.append({
|
||||
"node": pred,
|
||||
"relation": edge_data.get("relation", ""),
|
||||
"has_alternative": alternatives > 0,
|
||||
"alternative_count": alternatives,
|
||||
"status": edge_data.get("status", "正常"),
|
||||
})
|
||||
|
||||
return critical
|
||||
|
||||
|
||||
def find_all_risk_paths(G: nx.DiGraph, source: str, target: str, max_depth: int = 4) -> list:
|
||||
"""
|
||||
查找两个节点之间的所有风险路径
|
||||
"""
|
||||
if source not in G or target not in G:
|
||||
return []
|
||||
|
||||
try:
|
||||
paths = list(nx.all_simple_paths(G, source, target, cutoff=max_depth))
|
||||
return [
|
||||
{
|
||||
"path": p,
|
||||
"path_str": " → ".join(p),
|
||||
"length": len(p),
|
||||
}
|
||||
for p in paths
|
||||
]
|
||||
except nx.NetworkXError:
|
||||
return []
|
||||
@@ -0,0 +1,214 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
知识图谱构建引擎
|
||||
基于 NetworkX 构建科创企业供应链风险传染图谱
|
||||
节点类型:企业、供应商、客户、核心人员、制裁实体
|
||||
边类型:供应关系、客户关系、任职关系、投资关系
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import networkx as nx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DATA_DIR = Path(__file__).parent.parent / "data"
|
||||
|
||||
|
||||
def build_graph() -> nx.DiGraph:
|
||||
"""
|
||||
构建完整的科创企业供应链风险知识图谱
|
||||
"""
|
||||
G = nx.DiGraph()
|
||||
|
||||
# 1. 加载企业数据,添加企业节点
|
||||
_add_company_nodes(G)
|
||||
|
||||
# 2. 加载供应链数据,添加关系边
|
||||
_add_supply_chain_edges(G)
|
||||
|
||||
# 3. 加载实体清单,标记受制裁节点
|
||||
_mark_sanctioned_nodes(G)
|
||||
|
||||
logger.info(f"图谱构建完成: {G.number_of_nodes()} 节点, {G.number_of_edges()} 边")
|
||||
return G
|
||||
|
||||
|
||||
def _add_company_nodes(G: nx.DiGraph):
|
||||
"""添加科创板企业节点及其关联的核心人员节点"""
|
||||
filepath = DATA_DIR / "sample_companies.json"
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
companies = json.load(f)
|
||||
|
||||
for company in companies:
|
||||
name = company["short_name"]
|
||||
G.add_node(
|
||||
name,
|
||||
node_type="科创企业",
|
||||
stock_code=company["stock_code"],
|
||||
industry=company["industry"],
|
||||
sector=company["sector"],
|
||||
is_sanctioned=company["compliance"]["entity_list_status"] != "未列入",
|
||||
risk_level="正常",
|
||||
color="#4CAF50", # 默认绿色
|
||||
)
|
||||
|
||||
# 添加核心技术人员节点
|
||||
for person in company.get("core_tech_personnel", []):
|
||||
person_id = f"{person['name']}@{name}"
|
||||
G.add_node(
|
||||
person_id,
|
||||
node_type="核心人员",
|
||||
real_name=person["name"],
|
||||
title=person["title"],
|
||||
status=person["status"],
|
||||
importance=person["importance"],
|
||||
company=name,
|
||||
color="#2196F3", # 蓝色
|
||||
)
|
||||
G.add_edge(
|
||||
person_id, name,
|
||||
relation="任职于",
|
||||
edge_type="personnel",
|
||||
)
|
||||
|
||||
# 添加供应商节点
|
||||
for supplier in company.get("supply_chain", {}).get("key_suppliers", []):
|
||||
supplier_name = supplier.split("(")[0].split("(")[0].strip()
|
||||
if not G.has_node(supplier_name):
|
||||
G.add_node(
|
||||
supplier_name,
|
||||
node_type="供应商",
|
||||
is_sanctioned=False,
|
||||
risk_level="正常",
|
||||
color="#FF9800", # 橙色
|
||||
)
|
||||
G.add_edge(
|
||||
supplier_name, name,
|
||||
relation="供应",
|
||||
detail=supplier,
|
||||
edge_type="supply",
|
||||
)
|
||||
|
||||
# 添加客户节点
|
||||
for customer in company.get("supply_chain", {}).get("key_customers", []):
|
||||
customer_name = customer.split("(")[0].split("(")[0].strip()
|
||||
if not G.has_node(customer_name):
|
||||
G.add_node(
|
||||
customer_name,
|
||||
node_type="客户",
|
||||
is_sanctioned=False,
|
||||
risk_level="正常",
|
||||
color="#9C27B0", # 紫色
|
||||
)
|
||||
G.add_edge(
|
||||
name, customer_name,
|
||||
relation="供货给",
|
||||
edge_type="customer",
|
||||
)
|
||||
|
||||
|
||||
def _add_supply_chain_edges(G: nx.DiGraph):
|
||||
"""从供应链关系文件添加更详细的边"""
|
||||
filepath = DATA_DIR / "supply_chain.json"
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# 添加供应关系
|
||||
for rel in data.get("supply_relations", []):
|
||||
from_node = rel["from"]
|
||||
to_node = rel["to"]
|
||||
|
||||
# 确保节点存在
|
||||
if not G.has_node(from_node):
|
||||
G.add_node(from_node, node_type="供应商", is_sanctioned=False,
|
||||
risk_level="正常", color="#FF9800")
|
||||
if not G.has_node(to_node):
|
||||
G.add_node(to_node, node_type="企业", is_sanctioned=False,
|
||||
risk_level="正常", color="#4CAF50")
|
||||
|
||||
G.add_edge(
|
||||
from_node, to_node,
|
||||
relation=rel["relation"],
|
||||
critical=rel.get("critical", False),
|
||||
status=rel.get("status", "正常"),
|
||||
edge_type="supply",
|
||||
)
|
||||
|
||||
# 添加投资关系
|
||||
for rel in data.get("investment_relations", []):
|
||||
from_node = rel["from"]
|
||||
to_node = rel["to"]
|
||||
|
||||
if not G.has_node(from_node):
|
||||
G.add_node(from_node, node_type="投资方", is_sanctioned=False,
|
||||
risk_level="正常", color="#607D8B")
|
||||
|
||||
G.add_edge(
|
||||
from_node, to_node,
|
||||
relation=rel["relation"],
|
||||
share_ratio=rel.get("share_ratio", 0),
|
||||
edge_type="investment",
|
||||
)
|
||||
|
||||
|
||||
def _mark_sanctioned_nodes(G: nx.DiGraph):
|
||||
"""标记受制裁的节点,并向下游传播风险"""
|
||||
filepath = DATA_DIR / "entity_list.json"
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
entities = json.load(f)
|
||||
|
||||
# 收集所有受制裁实体的名称和别名
|
||||
sanctioned_names = set()
|
||||
for entity in entities:
|
||||
sanctioned_names.add(entity["entity_name"])
|
||||
for alias in entity.get("aliases", []):
|
||||
sanctioned_names.add(alias)
|
||||
|
||||
# 标记图谱中的受制裁节点
|
||||
for node in G.nodes():
|
||||
for sname in sanctioned_names:
|
||||
if node in sname or sname in node:
|
||||
G.nodes[node]["is_sanctioned"] = True
|
||||
G.nodes[node]["risk_level"] = "高危"
|
||||
G.nodes[node]["color"] = "#F44336" # 红色
|
||||
break
|
||||
|
||||
|
||||
def get_node_info(G: nx.DiGraph, node_name: str) -> dict:
|
||||
"""获取节点详细信息"""
|
||||
if node_name not in G:
|
||||
return {"error": f"节点 '{node_name}' 不存在"}
|
||||
|
||||
node_data = dict(G.nodes[node_name])
|
||||
predecessors = list(G.predecessors(node_name))
|
||||
successors = list(G.successors(node_name))
|
||||
|
||||
return {
|
||||
"name": node_name,
|
||||
"attributes": node_data,
|
||||
"upstream": predecessors,
|
||||
"downstream": successors,
|
||||
"degree": G.degree(node_name),
|
||||
}
|
||||
|
||||
|
||||
def get_graph_stats(G: nx.DiGraph) -> dict:
|
||||
"""获取图谱统计信息"""
|
||||
node_types = {}
|
||||
for _, data in G.nodes(data=True):
|
||||
t = data.get("node_type", "未知")
|
||||
node_types[t] = node_types.get(t, 0) + 1
|
||||
|
||||
sanctioned_count = sum(
|
||||
1 for _, data in G.nodes(data=True) if data.get("is_sanctioned", False)
|
||||
)
|
||||
|
||||
return {
|
||||
"total_nodes": G.number_of_nodes(),
|
||||
"total_edges": G.number_of_edges(),
|
||||
"node_types": node_types,
|
||||
"sanctioned_nodes": sanctioned_count,
|
||||
"density": nx.density(G),
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
知识图谱可视化模块
|
||||
使用 pyvis 生成交互式网络图,支持嵌入 Streamlit
|
||||
"""
|
||||
import logging
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import networkx as nx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# 节点类型对应的颜色和形状
|
||||
NODE_STYLES = {
|
||||
"科创企业": {"color": "#4CAF50", "shape": "dot", "size": 30},
|
||||
"供应商": {"color": "#FF9800", "shape": "diamond", "size": 20},
|
||||
"客户": {"color": "#9C27B0", "shape": "triangle", "size": 20},
|
||||
"核心人员": {"color": "#2196F3", "shape": "star", "size": 15},
|
||||
"投资方": {"color": "#607D8B", "shape": "square", "size": 20},
|
||||
"企业": {"color": "#4CAF50", "shape": "dot", "size": 25},
|
||||
}
|
||||
|
||||
# 受制裁节点的样式覆盖
|
||||
SANCTIONED_STYLE = {"color": "#F44336", "size": 35}
|
||||
# 受限边的样式
|
||||
RESTRICTED_EDGE_STYLE = {"color": "#F44336", "dashes": True, "width": 3}
|
||||
|
||||
|
||||
def generate_interactive_graph(
|
||||
G: nx.DiGraph,
|
||||
highlight_company: Optional[str] = None,
|
||||
output_path: Optional[str] = None,
|
||||
height: str = "600px",
|
||||
width: str = "100%",
|
||||
) -> str:
|
||||
"""
|
||||
生成交互式知识图谱 HTML
|
||||
"""
|
||||
try:
|
||||
from pyvis.network import Network
|
||||
except ImportError:
|
||||
logger.error("pyvis 未安装,请运行: pip install pyvis")
|
||||
return _generate_fallback_html(G)
|
||||
|
||||
net = Network(
|
||||
height=height,
|
||||
width=width,
|
||||
directed=True,
|
||||
notebook=False,
|
||||
bgcolor="#1a1a2e",
|
||||
font_color="white",
|
||||
)
|
||||
|
||||
# 物理引擎配置
|
||||
net.set_options("""
|
||||
{
|
||||
"physics": {
|
||||
"forceAtlas2Based": {
|
||||
"gravitationalConstant": -50,
|
||||
"centralGravity": 0.01,
|
||||
"springLength": 150,
|
||||
"springConstant": 0.08
|
||||
},
|
||||
"solver": "forceAtlas2Based",
|
||||
"stabilization": {"iterations": 100}
|
||||
},
|
||||
"interaction": {
|
||||
"hover": true,
|
||||
"tooltipDelay": 200,
|
||||
"navigationButtons": true
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
# 添加节点
|
||||
for node, data in G.nodes(data=True):
|
||||
node_type = data.get("node_type", "企业")
|
||||
style = NODE_STYLES.get(node_type, NODE_STYLES["企业"]).copy()
|
||||
|
||||
# 受制裁节点特殊样式
|
||||
if data.get("is_sanctioned", False):
|
||||
style.update(SANCTIONED_STYLE)
|
||||
|
||||
# 高亮选中企业
|
||||
if highlight_company and node == highlight_company:
|
||||
style["color"] = "#FFD700" # 金色
|
||||
style["size"] = 45
|
||||
style["borderWidth"] = 3
|
||||
|
||||
# 构建标签和悬浮提示
|
||||
label = node.split("@")[0] if "@" in node else node
|
||||
title_parts = [f"<b>{label}</b>", f"类型: {node_type}"]
|
||||
if data.get("is_sanctioned"):
|
||||
title_parts.append("⚠️ <b>已被制裁</b>")
|
||||
if data.get("stock_code"):
|
||||
title_parts.append(f"代码: {data['stock_code']}")
|
||||
if data.get("industry"):
|
||||
title_parts.append(f"行业: {data['industry']}")
|
||||
if data.get("title"):
|
||||
title_parts.append(f"职位: {data['title']}")
|
||||
if data.get("status"):
|
||||
title_parts.append(f"状态: {data['status']}")
|
||||
|
||||
net.add_node(
|
||||
node,
|
||||
label=label,
|
||||
title="<br>".join(title_parts),
|
||||
color=style["color"],
|
||||
shape=style["shape"],
|
||||
size=style["size"],
|
||||
)
|
||||
|
||||
# 添加边
|
||||
for u, v, data in G.edges(data=True):
|
||||
edge_style = {
|
||||
"color": "#666666",
|
||||
"width": 1,
|
||||
"arrows": "to",
|
||||
}
|
||||
|
||||
# 受限边特殊样式
|
||||
if data.get("status") == "受限":
|
||||
edge_style.update(RESTRICTED_EDGE_STYLE)
|
||||
elif data.get("critical", False):
|
||||
edge_style["width"] = 2
|
||||
edge_style["color"] = "#FFC107" # 关键边用黄色
|
||||
|
||||
relation = data.get("relation", "")
|
||||
net.add_edge(
|
||||
u, v,
|
||||
title=relation,
|
||||
label=relation if len(relation) <= 8 else "",
|
||||
**edge_style,
|
||||
)
|
||||
|
||||
# 输出 HTML
|
||||
if output_path is None:
|
||||
output_path = str(Path(tempfile.gettempdir()) / "kg_visualization.html")
|
||||
|
||||
net.save_graph(output_path)
|
||||
|
||||
# 读取 HTML 内容
|
||||
with open(output_path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def _generate_fallback_html(G: nx.DiGraph) -> str:
|
||||
"""当 pyvis 不可用时的备用 HTML 可视化"""
|
||||
nodes_info = []
|
||||
for node, data in G.nodes(data=True):
|
||||
label = node.split("@")[0] if "@" in node else node
|
||||
is_sanctioned = data.get("is_sanctioned", False)
|
||||
nodes_info.append(f"<li style='color: {'red' if is_sanctioned else 'green'}'>{label} ({data.get('node_type', '未知')})</li>")
|
||||
|
||||
return f"""
|
||||
<html><body style='background: #1a1a2e; color: white; padding: 20px;'>
|
||||
<h2>📊 知识图谱节点列表(pyvis 未安装,使用简化视图)</h2>
|
||||
<p>节点数: {G.number_of_nodes()} | 边数: {G.number_of_edges()}</p>
|
||||
<ul>{''.join(nodes_info[:50])}</ul>
|
||||
<p style='color: #888;'>安装 pyvis 以获得交互式可视化: pip install pyvis</p>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
|
||||
def get_subgraph_for_company(G: nx.DiGraph, company: str, depth: int = 2) -> nx.DiGraph:
|
||||
"""
|
||||
提取以指定企业为中心的子图(上下游 N 层)
|
||||
"""
|
||||
if company not in G:
|
||||
return nx.DiGraph()
|
||||
|
||||
# 收集相关节点
|
||||
related_nodes = {company}
|
||||
|
||||
# 上游(前驱)
|
||||
current_layer = {company}
|
||||
for _ in range(depth):
|
||||
next_layer = set()
|
||||
for node in current_layer:
|
||||
next_layer.update(G.predecessors(node))
|
||||
related_nodes.update(next_layer)
|
||||
current_layer = next_layer
|
||||
|
||||
# 下游(后继)
|
||||
current_layer = {company}
|
||||
for _ in range(depth):
|
||||
next_layer = set()
|
||||
for node in current_layer:
|
||||
next_layer.update(G.successors(node))
|
||||
related_nodes.update(next_layer)
|
||||
current_layer = next_layer
|
||||
|
||||
return G.subgraph(related_nodes).copy()
|
||||
@@ -0,0 +1,219 @@
|
||||
# -*- 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"<div style='text-align:center; font-size:1.2em;'>"
|
||||
f"{level_info['emoji']} 风险等级: <b>{level_info['level']}</b></div>",
|
||||
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"<div style='background:{color}22; padding:10px; border-radius:8px; "
|
||||
f"margin:5px 0; border-left:4px solid {color};'>"
|
||||
f"<b>{emoji} {detail['name']}</b>: {score}分 ({level['level']})</div>",
|
||||
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}")
|
||||
@@ -0,0 +1,177 @@
|
||||
# -*- 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 streamlit.components.v1 as components
|
||||
|
||||
from collectors.financial_collector import get_all_companies
|
||||
from knowledge_graph.graph_builder import build_graph, get_graph_stats
|
||||
from knowledge_graph.contagion_analyzer import analyze_contagion
|
||||
from knowledge_graph.graph_visualizer import (
|
||||
generate_interactive_graph,
|
||||
get_subgraph_for_company,
|
||||
)
|
||||
|
||||
st.set_page_config(page_title="供应链知识图谱", page_icon="🕸️", layout="wide")
|
||||
|
||||
st.markdown("# 🕸️ 供应链风险传染知识图谱")
|
||||
st.markdown("可视化科创企业供应链网络,识别风险传染路径和关键断裂节点。")
|
||||
|
||||
# 构建图谱
|
||||
@st.cache_resource
|
||||
def get_graph():
|
||||
return build_graph()
|
||||
|
||||
G = get_graph()
|
||||
stats = get_graph_stats(G)
|
||||
|
||||
# ============================================================
|
||||
# 图谱统计
|
||||
# ============================================================
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
with col1:
|
||||
st.metric("📌 总节点数", stats["total_nodes"])
|
||||
with col2:
|
||||
st.metric("🔗 总边数", stats["total_edges"])
|
||||
with col3:
|
||||
st.metric("⛔ 受制裁节点", stats["sanctioned_nodes"])
|
||||
with col4:
|
||||
st.metric("🔀 图密度", f"{stats['density']:.4f}")
|
||||
|
||||
# 节点类型分布
|
||||
with st.expander("📊 节点类型分布"):
|
||||
for ntype, count in stats["node_types"].items():
|
||||
st.markdown(f"- **{ntype}**: {count} 个")
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# ============================================================
|
||||
# 图谱视图选择
|
||||
# ============================================================
|
||||
view_mode = st.radio(
|
||||
"🔍 视图模式",
|
||||
["全局图谱", "企业中心视图(推荐)"],
|
||||
horizontal=True,
|
||||
)
|
||||
|
||||
from utils.session_helper import render_company_selector, render_sidebar_global_company_selector
|
||||
|
||||
with st.sidebar:
|
||||
render_sidebar_global_company_selector()
|
||||
st.markdown("---")
|
||||
|
||||
if view_mode == "企业中心视图(推荐)":
|
||||
target_company = render_company_selector("🏢 选择中心企业", key_suffix="graph_page")
|
||||
selected_company = target_company["short_name"] if target_company else "寒武纪"
|
||||
|
||||
depth = st.slider("穿透深度", 1, 3, 2)
|
||||
|
||||
# 提取子图
|
||||
subgraph = get_subgraph_for_company(G, selected_company, depth=depth)
|
||||
|
||||
if subgraph.number_of_nodes() > 0:
|
||||
html_content = generate_interactive_graph(
|
||||
subgraph,
|
||||
highlight_company=selected_company,
|
||||
height="550px",
|
||||
)
|
||||
components.html(html_content, height=600, scrolling=True)
|
||||
|
||||
# ============================================================
|
||||
# 供应链风险传染分析
|
||||
# ============================================================
|
||||
st.markdown("---")
|
||||
st.markdown(f"### ⚠️ {selected_company} 供应链风险传染分析")
|
||||
|
||||
contagion = analyze_contagion(G, selected_company)
|
||||
|
||||
# 风险评分
|
||||
supply_risk_score = contagion.get("risk_score", 0)
|
||||
if supply_risk_score >= 60:
|
||||
st.error(f"🔴 供应链风险评分: **{supply_risk_score}/100** — 供应链断裂风险极高")
|
||||
elif supply_risk_score >= 30:
|
||||
st.warning(f"🟡 供应链风险评分: **{supply_risk_score}/100** — 存在一定供应链风险")
|
||||
else:
|
||||
st.success(f"🟢 供应链风险评分: **{supply_risk_score}/100** — 供应链风险可控")
|
||||
|
||||
# 直接风险
|
||||
if contagion.get("direct_risks"):
|
||||
st.markdown("#### 🔴 直接风险(一度关联)")
|
||||
for risk in contagion["direct_risks"]:
|
||||
icon = "⛔" if risk.get("status") == "受限" else "⚠️"
|
||||
st.markdown(
|
||||
f"- {icon} **{risk['entity']}** ({risk['node_type']}) — "
|
||||
f"{risk['relation']} — 状态: {risk.get('status', '未知')} "
|
||||
f"{'🔑 关键供应' if risk.get('is_critical') else ''}"
|
||||
)
|
||||
|
||||
# 间接风险
|
||||
if contagion.get("indirect_risks"):
|
||||
st.markdown("#### 🟡 间接风险(二度及以上关联)")
|
||||
for risk in contagion["indirect_risks"]:
|
||||
st.markdown(
|
||||
f"- ⚠️ **{risk['entity']}** (距离: {risk['distance']}层) — {risk['relation']}"
|
||||
)
|
||||
|
||||
# 传染路径
|
||||
if contagion.get("contagion_paths"):
|
||||
st.markdown("#### 🔗 风险传染路径")
|
||||
for path in contagion["contagion_paths"]:
|
||||
severity_icon = "🔴" if path["severity"] == "高" else "🟡"
|
||||
st.markdown(f"- {severity_icon} `{path['path_str']}` (长度: {path['length']})")
|
||||
|
||||
# 关键断裂节点
|
||||
if contagion.get("critical_nodes"):
|
||||
st.markdown("#### 🔑 关键断裂节点")
|
||||
for node in contagion["critical_nodes"]:
|
||||
alt_info = f"✅ 有{node['alternative_count']}个替代" if node["has_alternative"] else "❌ 无替代方案"
|
||||
st.markdown(
|
||||
f"- **{node['node']}** — {node['relation']} — "
|
||||
f"状态: {node['status']} — {alt_info}"
|
||||
)
|
||||
else:
|
||||
st.warning(f"未找到 {selected_company} 的相关图谱数据")
|
||||
|
||||
else:
|
||||
# 全局视图
|
||||
st.info("💡 全局图谱节点较多,加载可能需要几秒钟。推荐使用“企业中心视图”获得更好的体验。")
|
||||
html_content = generate_interactive_graph(G, height="650px")
|
||||
components.html(html_content, height=700, scrolling=True)
|
||||
|
||||
# ============================================================
|
||||
# 图例
|
||||
# ============================================================
|
||||
st.markdown("---")
|
||||
st.markdown("#### 🎨 图例说明")
|
||||
col_l1, col_l2, col_l3 = st.columns(3)
|
||||
with col_l1:
|
||||
st.markdown("""
|
||||
**节点颜色**
|
||||
- 🟢 绿色: 科创企业 (正常)
|
||||
- 🟡 金色: 选中的中心企业
|
||||
- 🟠 橙色: 供应商
|
||||
- 🟣 紫色: 客户
|
||||
- 🔵 蓝色: 核心人员
|
||||
- 🔴 红色: 受制裁实体
|
||||
""")
|
||||
with col_l2:
|
||||
st.markdown("""
|
||||
**边类型**
|
||||
- 实线: 正常关系
|
||||
- 红色虚线: 受限关系
|
||||
- 黄色粗线: 关键供应关系
|
||||
""")
|
||||
with col_l3:
|
||||
st.markdown("""
|
||||
**交互操作**
|
||||
- 鼠标悬浮: 查看节点详情
|
||||
- 拖拽: 移动节点
|
||||
- 滚轮: 缩放图谱
|
||||
- 双击: 聚焦节点
|
||||
""")
|
||||
@@ -0,0 +1,540 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
⚖️ 多智能体辩论诊断页面 (Premium Financial-Grade Design)
|
||||
选择企业 → 真实 Token 级打字机流式推演 → 三方交叉质证 → 综合裁决与核保看板
|
||||
使用 Glassmorphism 玻璃拟态 + 暗黑金融科技 CSS 调色盘
|
||||
"""
|
||||
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 time
|
||||
|
||||
from collectors.financial_collector import get_all_companies, get_company_by_code
|
||||
from agents.debate_engine import DebateEngine
|
||||
|
||||
st.set_page_config(page_title="多智能体辩论诊断", page_icon="⚖️", layout="wide")
|
||||
|
||||
# ============================================================
|
||||
# 自定义 UI 视觉增强样式 (CSS 注入)
|
||||
# ============================================================
|
||||
st.markdown("""
|
||||
<style>
|
||||
/* 引入 Google 科技字体 */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&family=Inter:wght@400;600;700&display=swap');
|
||||
|
||||
/* 顶栏 Hero 区域 */
|
||||
.hero-banner {
|
||||
background: linear-gradient(135deg, rgba(26, 26, 46, 0.95), rgba(22, 33, 62, 0.9), rgba(15, 52, 96, 0.95));
|
||||
border: 1px solid rgba(233, 69, 96, 0.25);
|
||||
border-radius: 16px;
|
||||
padding: 24px 30px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.37);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
.hero-title {
|
||||
color: #FFFFFF;
|
||||
font-size: 2.0rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 8px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.hero-subtitle {
|
||||
color: #94A3B8;
|
||||
font-size: 1.0rem;
|
||||
margin: 0;
|
||||
}
|
||||
.step-badge-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.step-badge {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
color: #CBD5E1;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.step-badge-active {
|
||||
background: rgba(233, 69, 96, 0.15);
|
||||
border-color: #e94560;
|
||||
color: #ff758c;
|
||||
}
|
||||
|
||||
/* 智能体研判卡片 (Glassmorphism) */
|
||||
.agent-card {
|
||||
background: rgba(22, 33, 62, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 14px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
}
|
||||
.agent-card:hover {
|
||||
border-color: rgba(233, 69, 96, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.agent-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.agent-name {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
color: #F8FAFC;
|
||||
}
|
||||
|
||||
/* 风险指标 Badge */
|
||||
.risk-pill {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.pill-high { background: rgba(244, 67, 54, 0.2); color: #ff6b6b; border: 1px solid rgba(244, 67, 54, 0.4); }
|
||||
.pill-med { background: rgba(255, 152, 0, 0.2); color: #ffb74d; border: 1px solid rgba(255, 152, 0, 0.4); }
|
||||
.pill-low { background: rgba(76, 175, 80, 0.2); color: #81c784; border: 1px solid rgba(76, 175, 80, 0.4); }
|
||||
|
||||
/* 模拟黑客流式终端 (Terminal Box) */
|
||||
.terminal-box {
|
||||
background: #0B0E14;
|
||||
border: 1px solid #1E293B;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 0.84rem;
|
||||
color: #38BDF8;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.terminal-thinking {
|
||||
color: #94A3B8;
|
||||
font-style: italic;
|
||||
border-left: 2px solid #6366F1;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.terminal-output {
|
||||
color: #4ADE80;
|
||||
border-left: 2px solid #22C55E;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
/* 裁决结果看板 */
|
||||
.verdict-banner {
|
||||
background: linear-gradient(135deg, #1E1B4B, #31103F);
|
||||
border: 1px solid #6366F1;
|
||||
border-radius: 14px;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.verdict-title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# ============================================================
|
||||
# 页面顶部 Banner
|
||||
# ============================================================
|
||||
st.markdown("""
|
||||
<div class="hero-banner">
|
||||
<div class="hero-title">
|
||||
<span>⚖️</span>
|
||||
<span>多智能体交叉验证辩论诊断</span>
|
||||
</div>
|
||||
<div class="hero-subtitle">
|
||||
基于 Multi-Agent 辩论图谱 · 法务 / 技术 / 财务专家分布式研判 · 全过程打字机流式追溯
|
||||
</div>
|
||||
<div class="step-badge-container">
|
||||
<span class="step-badge step-badge-active">Phase 1: 独立穿透研判</span>
|
||||
<span class="step-badge">Phase 2: 三方交叉质证</span>
|
||||
<span class="step-badge">Phase 3: 委员会综合裁决</span>
|
||||
<span class="step-badge">🛡️ 保险精算核保建议</span>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
from utils.session_helper import render_company_selector, render_sidebar_global_company_selector
|
||||
|
||||
with st.sidebar:
|
||||
render_sidebar_global_company_selector()
|
||||
st.markdown("---")
|
||||
|
||||
col_ctrl1, col_ctrl2 = st.columns([3, 1])
|
||||
with col_ctrl1:
|
||||
company = render_company_selector("🏢 选择待诊断科创企业", key_suffix="debate_page")
|
||||
stock_code = company["stock_code"] if company else "688256"
|
||||
|
||||
# 初始化 session_state
|
||||
if "debate_results" not in st.session_state:
|
||||
st.session_state["debate_results"] = None
|
||||
|
||||
with col_ctrl2:
|
||||
st.markdown("<div style='height: 28px;'></div>", unsafe_allow_html=True)
|
||||
start_btn = st.button("🚀 启动多智能体辩论", type="primary", use_container_width=True)
|
||||
|
||||
# ============================================================
|
||||
# 辩论触发与打字机流式渲染
|
||||
# ============================================================
|
||||
if start_btn:
|
||||
company = get_company_by_code(stock_code)
|
||||
if not company:
|
||||
st.error("未找到企业数据")
|
||||
st.stop()
|
||||
|
||||
engine = DebateEngine()
|
||||
company_name = company.get("short_name", "未知")
|
||||
|
||||
st.markdown("---")
|
||||
st.markdown("### 📡 智能体实时思考与流式推演控制台 (Live Streaming Terminal)")
|
||||
progress_bar = st.progress(0, text="▶️ 正在初始化分布式智能体网络...")
|
||||
|
||||
# 动态渲染控制台
|
||||
live_box = st.container(border=True)
|
||||
|
||||
def run_agent_with_typewriter(agent, agent_title, start_progress, end_progress):
|
||||
progress_bar.progress(start_progress, text=f"{agent.role_icon} {agent_title} 正在穿透企业数据并与大模型通信...")
|
||||
with live_box:
|
||||
st.markdown(f"##### {agent.role_icon} {agent_title}")
|
||||
status_placeholder = st.empty()
|
||||
thinking_placeholder = st.empty()
|
||||
content_placeholder = st.empty()
|
||||
|
||||
thinking_buf = []
|
||||
content_buf = []
|
||||
|
||||
status_placeholder.info("🔗 建立 SSE 流式通信中...")
|
||||
|
||||
def on_token(token_type: str, token_text: str):
|
||||
if token_type == "reasoning":
|
||||
thinking_buf.append(token_text)
|
||||
t_str = "".join(thinking_buf)
|
||||
status_placeholder.markdown("🧠 **[大模型深度思考中...]**")
|
||||
display_text = t_str[-350:] if len(t_str) > 350 else t_str
|
||||
thinking_placeholder.markdown(
|
||||
f"""<div class="terminal-box terminal-thinking">
|
||||
<b>[THINKING]</b> {display_text}▌
|
||||
</div>""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
elif token_type == "content":
|
||||
content_buf.append(token_text)
|
||||
c_str = "".join(content_buf)
|
||||
status_placeholder.markdown("📝 **[正在实时生成审查报告...]**")
|
||||
display_c = c_str[-280:] if len(c_str) > 280 else c_str
|
||||
content_placeholder.markdown(
|
||||
f"""<div class="terminal-box terminal-output">
|
||||
<b>[REPORT OUTPUT]</b> {display_c}▌
|
||||
</div>""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
agent.on_token_callback = on_token
|
||||
result = agent.evaluate(company if agent != engine.judge_agent else None)
|
||||
progress_bar.progress(end_progress, text=f"✅ {agent_title} 研判完毕")
|
||||
status_placeholder.success(f"✅ {agent_title} 完成风险评级")
|
||||
return result, list(agent.reasoning_trace)
|
||||
|
||||
# 1. 法务 Agent
|
||||
law_result, law_trace = run_agent_with_typewriter(engine.law_agent, "法务风控节点", 10, 35)
|
||||
|
||||
# 2. 技术 Agent
|
||||
tech_result, tech_trace = run_agent_with_typewriter(engine.tech_agent, "技术风控节点", 35, 60)
|
||||
|
||||
# 3. 财务 Agent
|
||||
finance_result, finance_trace = run_agent_with_typewriter(engine.finance_agent, "财务风控节点", 60, 85)
|
||||
|
||||
# 4. 交叉质证
|
||||
progress_bar.progress(88, text="🔄 正在比对三方判定结论,进行交叉质证分析...")
|
||||
conflicts = engine._identify_conflicts(law_result, tech_result, finance_result)
|
||||
|
||||
# 5. 综合裁决 Agent
|
||||
progress_bar.progress(92, text="⚖️ 综合裁决委员会进行权重复核...")
|
||||
with live_box:
|
||||
st.markdown("##### ⚖️ 综合裁决节点")
|
||||
j_status = st.empty()
|
||||
j_thinking = st.empty()
|
||||
j_content = st.empty()
|
||||
|
||||
j_think_buf = []
|
||||
j_cont_buf = []
|
||||
|
||||
j_status.info("🚀 综合裁决委员会正在消解分歧...")
|
||||
|
||||
def judge_on_token(token_type: str, token_text: str):
|
||||
if token_type == "reasoning":
|
||||
j_think_buf.append(token_text)
|
||||
t_str = "".join(j_think_buf)
|
||||
j_status.markdown("🧠 **[裁决委员会讨论中...]**")
|
||||
display_t = t_str[-350:] if len(t_str) > 350 else t_str
|
||||
j_thinking.markdown(
|
||||
f"""<div class="terminal-box terminal-thinking">
|
||||
<b>[COMMITTEE THOUGHTS]</b> {display_t}▌
|
||||
</div>""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
elif token_type == "content":
|
||||
j_cont_buf.append(token_text)
|
||||
c_str = "".join(j_cont_buf)
|
||||
j_status.markdown("📝 **[生成核保决议中...]**")
|
||||
display_c = c_str[-280:] if len(c_str) > 280 else c_str
|
||||
j_content.markdown(
|
||||
f"""<div class="terminal-box terminal-output">
|
||||
<b>[FINAL VERDICT]</b> {display_c}▌
|
||||
</div>""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
engine.judge_agent.on_token_callback = judge_on_token
|
||||
judge_result = engine.judge_agent.evaluate(company, law_result, tech_result, finance_result)
|
||||
judge_trace = list(engine.judge_agent.reasoning_trace)
|
||||
j_status.success("✅ 综合裁决完成")
|
||||
|
||||
progress_bar.progress(100, text="✅ 辩论诊断流程全量完成!")
|
||||
|
||||
# 持久化结果
|
||||
st.session_state["debate_results"] = {
|
||||
"company": company,
|
||||
"law_result": law_result,
|
||||
"tech_result": tech_result,
|
||||
"finance_result": finance_result,
|
||||
"law_trace": law_trace,
|
||||
"tech_trace": tech_trace,
|
||||
"finance_trace": finance_trace,
|
||||
"judge_trace": judge_trace,
|
||||
"conflicts": conflicts,
|
||||
"judge_result": judge_result,
|
||||
"debate_log": engine.debate_log,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 结果持久化渲染与面板展示
|
||||
# ============================================================
|
||||
def render_trace_expander(trace: list, title: str):
|
||||
"""渲染研判结果对应的详细辩论链"""
|
||||
with st.expander(f"🔗 查看 【{title}】 详细辩论链与凭据 (共 {len(trace)} 步)", expanded=False):
|
||||
for step in trace:
|
||||
ts = step.get("timestamp", "")
|
||||
step_name = step.get("step", "")
|
||||
content = step.get("content", "")
|
||||
|
||||
if "思维链" in step_name or "原始" in step_name or "Context" in step_name or len(content) > 150:
|
||||
st.markdown(f"**`[{ts}]` {step_name}**")
|
||||
st.code(content, language="text")
|
||||
else:
|
||||
st.markdown(f"- **`[{ts}]` {step_name}**: {content}")
|
||||
|
||||
|
||||
def render_risk_card(result: dict, keys: list, trace: list, agent_title: str, overall_key: str):
|
||||
"""渲染三方风控精美卡片"""
|
||||
overall = result.get(overall_key, {}).get("score", 50)
|
||||
level = result.get(overall_key, {}).get("level", "中")
|
||||
|
||||
pill_class = "pill-high" if level == "高" else ("pill-med" if level == "中" else "pill-low")
|
||||
|
||||
st.markdown(f"""
|
||||
<div class="agent-card">
|
||||
<div class="agent-header">
|
||||
<span class="agent-name">{agent_title}</span>
|
||||
<span><span class="risk-pill {pill_class}">{level}风险</span> <b>{overall}分</b></span>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# 细分指标渲染
|
||||
for key in keys:
|
||||
item = result.get(key, {})
|
||||
if isinstance(item, dict) and "detail" in item:
|
||||
score = item.get("score", 0)
|
||||
color = "#EF4444" if score >= 70 else ("#F59E0B" if score >= 40 else "#10B981")
|
||||
st.markdown(
|
||||
f"<div style='padding:8px 12px; border-radius:6px; margin:6px 0; "
|
||||
f"border-left:4px solid {color}; background:rgba(255,255,255,0.03); font-size:0.88rem;'>"
|
||||
f"<b style='color:{color};'>{score}分</b> — {item.get('detail', '')}</div>",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
# 关键发现
|
||||
findings = result.get("key_findings", [])
|
||||
if findings:
|
||||
st.markdown("<p style='font-size:0.85rem; font-weight:700; color:#94A3B8; margin-top:12px; margin-bottom:4px;'>📌 关键风险发现:</p>", unsafe_allow_html=True)
|
||||
for f in findings:
|
||||
st.markdown(f"<span style='font-size:0.83rem; color:#E2E8F0;'>• {f}</span>", unsafe_allow_html=True)
|
||||
|
||||
# 建议
|
||||
recs = result.get("recommendations", [])
|
||||
if recs:
|
||||
st.markdown("<p style='font-size:0.85rem; font-weight:700; color:#94A3B8; margin-top:10px; margin-bottom:4px;'>💡 专家处置建议:</p>", unsafe_allow_html=True)
|
||||
for r in recs:
|
||||
st.markdown(f"<span style='font-size:0.83rem; color:#CBD5E1;'>• {r}</span>", unsafe_allow_html=True)
|
||||
|
||||
st.markdown("</div>", unsafe_allow_html=True)
|
||||
st.markdown("<br>", unsafe_allow_html=True)
|
||||
render_trace_expander(trace, agent_title)
|
||||
|
||||
|
||||
res = st.session_state.get("debate_results")
|
||||
|
||||
if res is not None:
|
||||
company = res["company"]
|
||||
law_result = res["law_result"]
|
||||
tech_result = res["tech_result"]
|
||||
finance_result = res["finance_result"]
|
||||
conflicts = res["conflicts"]
|
||||
judge_result = res["judge_result"]
|
||||
|
||||
st.markdown("---")
|
||||
st.markdown(f"### 📊 多智能体交叉辩论诊断报告 — {company['short_name']} ({company['stock_code']})")
|
||||
|
||||
# ---- Phase 1: 三方独立研判 ----
|
||||
st.markdown("#### 📋 Phase 1: 三方专家节点穿透研判全景")
|
||||
col_law, col_tech, col_fin = st.columns(3)
|
||||
|
||||
with col_law:
|
||||
render_risk_card(
|
||||
law_result,
|
||||
["algo_compliance_risk", "geopolitical_risk", "data_compliance_risk", "ip_litigation_risk"],
|
||||
res.get("law_trace", []),
|
||||
"👩⚖️ 法务风控节点",
|
||||
"overall_law_risk",
|
||||
)
|
||||
|
||||
with col_tech:
|
||||
render_risk_card(
|
||||
tech_result,
|
||||
["tech_disruption_risk", "talent_loss_risk", "patent_moat", "tech_iteration_pressure"],
|
||||
res.get("tech_trace", []),
|
||||
"👨🔬 技术风控节点",
|
||||
"overall_tech_risk",
|
||||
)
|
||||
|
||||
with col_fin:
|
||||
render_risk_card(
|
||||
finance_result,
|
||||
["rd_capitalization_risk", "concentration_risk", "receivable_risk", "cashflow_risk"],
|
||||
res.get("finance_trace", []),
|
||||
"👔 财务风控节点",
|
||||
"overall_fin_risk",
|
||||
)
|
||||
|
||||
# ---- Phase 2: 交叉质证分析 ----
|
||||
st.markdown("---")
|
||||
st.markdown("#### 🔄 Phase 2: 三方交叉质证与冲突判定")
|
||||
if conflicts:
|
||||
for conflict in conflicts:
|
||||
st.markdown(f"""
|
||||
<div style='background:rgba(239, 68, 68, 0.1); border:1px solid rgba(239, 68, 68, 0.3); border-radius:8px; padding:12px 16px; margin:6px 0; color:#FCA5A5;'>
|
||||
⚠️ <b>判定冲突警示</b>:{conflict}
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
else:
|
||||
st.markdown("""
|
||||
<div style='background:rgba(16, 185, 129, 0.1); border:1px solid rgba(16, 185, 129, 0.3); border-radius:8px; padding:12px 16px; color:#6EE7B7;'>
|
||||
✅ <b>一致性确认</b>:法务、技术、财务三方判定逻辑高度契合,无重大矛盾分歧。
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# ---- Phase 3: 综合裁决与保险核保 ----
|
||||
st.markdown("---")
|
||||
st.markdown("#### ⚖️ Phase 3: 风险委员会综合裁决与核保决策")
|
||||
|
||||
comp_score = judge_result.get("comprehensive_score", 50)
|
||||
decision = judge_result.get("underwriting_decision", "标准承保")
|
||||
risk_level = judge_result.get("risk_level", "中")
|
||||
|
||||
col_j1, col_j2 = st.columns([1, 1])
|
||||
|
||||
with col_j1:
|
||||
# Plotly 仪表盘 (暗黑高精质感)
|
||||
fig = go.Figure(go.Indicator(
|
||||
mode="gauge+number",
|
||||
value=comp_score,
|
||||
title={"text": "综合风险指数 (0-100)", "font": {"color": "#F8FAFC", "size": 16}},
|
||||
number={"font": {"color": "#FFFFFF", "size": 48}},
|
||||
gauge={
|
||||
"axis": {"range": [0, 100], "tickcolor": "#94A3B8"},
|
||||
"bar": {"color": "#e94560", "width": 0.3},
|
||||
"bgcolor": "rgba(0,0,0,0)",
|
||||
"bordercolor": "rgba(255,255,255,0.1)",
|
||||
"steps": [
|
||||
{"range": [0, 40], "color": "rgba(16, 185, 129, 0.25)"},
|
||||
{"range": [40, 60], "color": "rgba(245, 158, 11, 0.25)"},
|
||||
{"range": [60, 80], "color": "rgba(249, 115, 22, 0.25)"},
|
||||
{"range": [80, 100], "color": "rgba(239, 68, 68, 0.25)"},
|
||||
],
|
||||
},
|
||||
))
|
||||
fig.update_layout(
|
||||
height=260,
|
||||
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)
|
||||
|
||||
with col_j2:
|
||||
if "拒绝" in decision:
|
||||
v_color = "#EF4444"
|
||||
v_bg = "rgba(239, 68, 68, 0.15)"
|
||||
elif "附条件" in decision:
|
||||
v_color = "#F59E0B"
|
||||
v_bg = "rgba(245, 158, 11, 0.15)"
|
||||
elif "优先" in decision:
|
||||
v_color = "#10B981"
|
||||
v_bg = "rgba(16, 185, 129, 0.15)"
|
||||
else:
|
||||
v_color = "#3B82F6"
|
||||
v_bg = "rgba(59, 130, 246, 0.15)"
|
||||
|
||||
st.markdown(f"""
|
||||
<div class="verdict-banner" style="border-color:{v_color}; background:{v_bg};">
|
||||
<div class="verdict-title" style="color:{v_color};">核保决策: 【{decision}】</div>
|
||||
<p style="color:#CBD5E1; margin-top:8px;">综合风险等级: <b>{risk_level}</b> | 加权裁决得分: <b>{comp_score}分</b></p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown(f"**📝 裁决终审意见**: {judge_result.get('summary', '')}")
|
||||
|
||||
conditions = judge_result.get("underwriting_conditions", [])
|
||||
if conditions:
|
||||
st.markdown("**📌 保险核保附加条件**:")
|
||||
for c in conditions:
|
||||
st.markdown(f"- <span style='color:#F59E0B;'>{c}</span>", unsafe_allow_html=True)
|
||||
|
||||
# 挂载裁决节点的辩论链
|
||||
st.markdown("<br>", unsafe_allow_html=True)
|
||||
render_trace_expander(res.get("judge_trace", []), "⚖️ 综合裁决委员会")
|
||||
|
||||
else:
|
||||
# 默认引导说明
|
||||
st.markdown("""
|
||||
<div style='background:rgba(255,255,255,0.02); border:1px solid rgba(255,255,255,0.08); border-radius:12px; padding:24px; margin-top:20px;'>
|
||||
<h4 style='color:#F8FAFC; margin-top:0;'>💡 多智能体辩论与风险识别流程说明</h4>
|
||||
<ol style='color:#94A3B8; line-height:1.8;'>
|
||||
<li><b>Phase 1 - 三方穿透研判</b>: 法务风控节点审查算法与出口管制、技术风控节点审查路线与人员、财务风控节点穿透资本化与集中度。</li>
|
||||
<li><b>Phase 2 - 交叉质证分析</b>: 识别法务、技术、财务意见间的潜在分歧(如:研发投入大 vs 资本化美化利润)。</li>
|
||||
<li><b>Phase 3 - 委员会综合裁决</b>: 基于【合规 > 技术 > 财务】优先级规则进行加权综合评分并输出精算核保决议。</li>
|
||||
</ol>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
@@ -0,0 +1,283 @@
|
||||
# -*- 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,
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
streamlit>=1.30.0
|
||||
pandas>=2.0.0
|
||||
networkx>=3.0
|
||||
pyvis>=0.3.2
|
||||
plotly>=5.18.0
|
||||
requests>=2.31.0
|
||||
openai>=1.10.0
|
||||
pdfplumber>=0.10.0
|
||||
PyPDF2>=3.0.0
|
||||
akshare>=1.12.0
|
||||
jieba>=0.42.1
|
||||
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""风险评估引擎模块"""
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
全局状态与企业选择同步工具
|
||||
跨页面共享 `st.session_state["global_selected_stock_code"]`
|
||||
确保在一个页面选择企业后,切换到任意页面均自动保持该企业联动
|
||||
包含自动隐藏 Streamlit 右上角 Deploy 按钮与默认 Header 的全局 CSS 样式
|
||||
"""
|
||||
import streamlit as st
|
||||
from collectors.financial_collector import get_all_companies
|
||||
|
||||
GLOBAL_COMPANY_KEY = "global_selected_stock_code"
|
||||
|
||||
|
||||
def hide_streamlit_header_footer():
|
||||
"""彻底隐藏 Streamlit 右上角的 Deploy 按钮、工具栏及页脚"""
|
||||
st.markdown("""
|
||||
<style>
|
||||
/* 隐藏右上角 Deploy 按钮及 Header 菜单 */
|
||||
header[data-testid="stHeader"] {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
height: 0px !important;
|
||||
}
|
||||
[data-testid="stDeployButton"] {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
#MainMenu {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
footer {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
|
||||
def get_global_company_code() -> str:
|
||||
"""获取当前全局选中的企业股票代码"""
|
||||
companies = get_all_companies()
|
||||
if GLOBAL_COMPANY_KEY not in st.session_state or not st.session_state[GLOBAL_COMPANY_KEY]:
|
||||
st.session_state[GLOBAL_COMPANY_KEY] = companies[0]["stock_code"] if companies else "688256"
|
||||
return st.session_state[GLOBAL_COMPANY_KEY]
|
||||
|
||||
|
||||
def set_global_company_code(code: str):
|
||||
"""保存选中的企业股票代码到全局 SessionState"""
|
||||
st.session_state[GLOBAL_COMPANY_KEY] = code
|
||||
|
||||
|
||||
def render_company_selector(label: str = "🏢 选择待评估科创企业", key_suffix: str = "main"):
|
||||
"""
|
||||
渲染与全局 session_state 双向同步的企业选择下拉框
|
||||
返回: selected_company_dict (选中的企业数据字典)
|
||||
"""
|
||||
hide_streamlit_header_footer()
|
||||
|
||||
companies = get_all_companies()
|
||||
if not companies:
|
||||
return None
|
||||
|
||||
# 构建带图标与领域的名称映射
|
||||
company_options = {f"{c['short_name']} ({c['stock_code']}) - {c['sector']}": c["stock_code"] for c in companies}
|
||||
labels_list = list(company_options.keys())
|
||||
codes_list = [c["stock_code"] for c in companies]
|
||||
|
||||
current_code = get_global_company_code()
|
||||
default_index = codes_list.index(current_code) if current_code in codes_list else 0
|
||||
|
||||
selected_label = st.selectbox(
|
||||
label,
|
||||
labels_list,
|
||||
index=default_index,
|
||||
key=f"company_selector_{key_suffix}"
|
||||
)
|
||||
|
||||
new_code = company_options[selected_label]
|
||||
if new_code != st.session_state.get(GLOBAL_COMPANY_KEY):
|
||||
set_global_company_code(new_code)
|
||||
|
||||
# 返回选中的完整企业字典
|
||||
for c in companies:
|
||||
if c["stock_code"] == new_code:
|
||||
return c
|
||||
return companies[0]
|
||||
|
||||
|
||||
def render_sidebar_global_company_selector():
|
||||
"""在侧边栏渲染全局企业选择器与状态指示标签,并自动注入隐藏 Deploy 的 CSS"""
|
||||
hide_streamlit_header_footer()
|
||||
|
||||
companies = get_all_companies()
|
||||
if not companies:
|
||||
return
|
||||
|
||||
company_options = {f"{c['short_name']} ({c['stock_code']})": c["stock_code"] for c in companies}
|
||||
labels_list = list(company_options.keys())
|
||||
codes_list = [c["stock_code"] for c in companies]
|
||||
|
||||
current_code = get_global_company_code()
|
||||
default_index = codes_list.index(current_code) if current_code in codes_list else 0
|
||||
|
||||
st.markdown("### 🏢 全局联动评估目标")
|
||||
selected_label = st.selectbox(
|
||||
"当前联动目标企业:",
|
||||
labels_list,
|
||||
index=default_index,
|
||||
key="global_sidebar_company_selector"
|
||||
)
|
||||
|
||||
new_code = company_options[selected_label]
|
||||
if new_code != st.session_state.get(GLOBAL_COMPANY_KEY):
|
||||
set_global_company_code(new_code)
|
||||
@@ -0,0 +1,304 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
🛡️ 科创企业智能风控与核保系统 - 首页 / 风控大屏入口
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
|
||||
# 确保项目根目录在 Python 路径中
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from config import PAGE_TITLE, PAGE_ICON, LAYOUT
|
||||
from collectors.financial_collector import get_all_companies
|
||||
from risk_engine.risk_scorer import calculate_six_dimension_scores, get_risk_level
|
||||
|
||||
# ============================================================
|
||||
# 页面配置
|
||||
# ============================================================
|
||||
st.set_page_config(
|
||||
page_title=PAGE_TITLE,
|
||||
page_icon=PAGE_ICON,
|
||||
layout=LAYOUT,
|
||||
initial_sidebar_state="expanded",
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# 自定义样式
|
||||
# ============================================================
|
||||
st.markdown("""
|
||||
<style>
|
||||
/* 主标题渐变 */
|
||||
.main-title {
|
||||
background: linear-gradient(120deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
||||
}
|
||||
.main-title h1 {
|
||||
color: #e94560;
|
||||
font-size: 2.2em;
|
||||
margin: 0;
|
||||
}
|
||||
.main-title p {
|
||||
color: #a8a8b3;
|
||||
font-size: 1.1em;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #1a1a2e, #16213e);
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
border: 1px solid #2a2a4a;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.stat-card .number {
|
||||
font-size: 2.5em;
|
||||
font-weight: bold;
|
||||
color: #e94560;
|
||||
}
|
||||
.stat-card .label {
|
||||
color: #a8a8b3;
|
||||
font-size: 0.95em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* 企业卡片 */
|
||||
.company-card {
|
||||
background: #16213e;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin: 8px 0;
|
||||
border-left: 4px solid;
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
[data-testid="stSidebar"] {
|
||||
background: linear-gradient(180deg, #1a1a2e 0%, #0f0f23 100%);
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
from utils.session_helper import render_sidebar_global_company_selector
|
||||
|
||||
# ============================================================
|
||||
# 侧边栏
|
||||
# ============================================================
|
||||
with st.sidebar:
|
||||
render_sidebar_global_company_selector()
|
||||
st.markdown("---")
|
||||
st.markdown("### 🛡️ 系统导航")
|
||||
st.markdown("---")
|
||||
st.markdown("""
|
||||
**功能模块**
|
||||
- 🏠 系统首页
|
||||
- 📊 企业风险概览
|
||||
- 🕸️ 供应链知识图谱
|
||||
- ⚖️ 多智能体辩论诊断
|
||||
- 💰 动态定价与核保
|
||||
""")
|
||||
st.markdown("---")
|
||||
st.markdown("""
|
||||
**技术栈**
|
||||
- 🤖 DeepSeek API (LLM)
|
||||
- 🕸️ NetworkX (知识图谱)
|
||||
- 📊 Plotly (可视化)
|
||||
- 🔧 Streamlit (Web框架)
|
||||
""")
|
||||
st.markdown("---")
|
||||
st.caption("中国平安 × 挑战杯 · 科创风控原型")
|
||||
|
||||
# ============================================================
|
||||
# 主页内容
|
||||
# ============================================================
|
||||
|
||||
# 标题
|
||||
st.markdown("""
|
||||
<div class="main-title">
|
||||
<h1>🛡️ 科创企业智能风控与核保系统</h1>
|
||||
<p>基于多智能体辩论 × 知识图谱 × 动态定价的全链条风控平台</p>
|
||||
<p style="font-size: 0.85em; color: #666;">中国青基会平安励志计划 · XH-202626 · 科创企业特有风险的识别与管理</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# 加载企业数据
|
||||
companies = get_all_companies()
|
||||
|
||||
# ============================================================
|
||||
# 统计概览卡片
|
||||
# ============================================================
|
||||
st.markdown("### 📊 系统概览")
|
||||
|
||||
# 计算所有企业的风险评分
|
||||
risk_data = []
|
||||
for comp in companies:
|
||||
scores = calculate_six_dimension_scores(comp)
|
||||
risk_data.append({
|
||||
"company": comp["short_name"],
|
||||
"stock_code": comp["stock_code"],
|
||||
"industry": comp["industry"],
|
||||
"sector": comp["sector"],
|
||||
"comprehensive_score": scores["comprehensive_score"],
|
||||
"risk_level": scores["risk_level"]["level"],
|
||||
**scores["scores"],
|
||||
})
|
||||
|
||||
df = pd.DataFrame(risk_data)
|
||||
|
||||
# 统计卡片
|
||||
col1, col2, col3, col4, col5 = st.columns(5)
|
||||
|
||||
with col1:
|
||||
st.markdown(f"""
|
||||
<div class="stat-card">
|
||||
<div class="number">{len(companies)}</div>
|
||||
<div class="label">监控企业总数</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with col2:
|
||||
high_risk = len(df[df["comprehensive_score"] >= 70])
|
||||
st.markdown(f"""
|
||||
<div class="stat-card">
|
||||
<div class="number" style="color: #F44336;">{high_risk}</div>
|
||||
<div class="label">⚠️ 高风险企业</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with col3:
|
||||
med_risk = len(df[(df["comprehensive_score"] >= 40) & (df["comprehensive_score"] < 70)])
|
||||
st.markdown(f"""
|
||||
<div class="stat-card">
|
||||
<div class="number" style="color: #FF9800;">{med_risk}</div>
|
||||
<div class="label">🟡 中风险企业</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with col4:
|
||||
low_risk = len(df[df["comprehensive_score"] < 40])
|
||||
st.markdown(f"""
|
||||
<div class="stat-card">
|
||||
<div class="number" style="color: #4CAF50;">{low_risk}</div>
|
||||
<div class="label">🟢 低风险企业</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with col5:
|
||||
sanctioned = len([c for c in companies if "被列入" in c.get("compliance", {}).get("entity_list_status", "")])
|
||||
st.markdown(f"""
|
||||
<div class="stat-card">
|
||||
<div class="number" style="color: #B71C1C;">{sanctioned}</div>
|
||||
<div class="label">⛔ 受制裁企业</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown("<br>", unsafe_allow_html=True)
|
||||
|
||||
# ============================================================
|
||||
# 风险分布图
|
||||
# ============================================================
|
||||
col_left, col_right = st.columns([3, 2])
|
||||
|
||||
with col_left:
|
||||
st.markdown("#### 🎯 企业综合风险评分分布")
|
||||
|
||||
# 水平柱状图,按风险排序
|
||||
df_sorted = df.sort_values("comprehensive_score", ascending=True)
|
||||
|
||||
colors = []
|
||||
for score in df_sorted["comprehensive_score"]:
|
||||
if score >= 70:
|
||||
colors.append("#F44336")
|
||||
elif score >= 50:
|
||||
colors.append("#FF9800")
|
||||
elif score >= 30:
|
||||
colors.append("#FFC107")
|
||||
else:
|
||||
colors.append("#4CAF50")
|
||||
|
||||
fig = go.Figure(go.Bar(
|
||||
x=df_sorted["comprehensive_score"],
|
||||
y=df_sorted["company"],
|
||||
orientation="h",
|
||||
marker_color=colors,
|
||||
text=df_sorted["comprehensive_score"],
|
||||
textposition="outside",
|
||||
))
|
||||
fig.update_layout(
|
||||
height=400,
|
||||
margin=dict(l=0, r=30, t=10, b=10),
|
||||
plot_bgcolor="rgba(0,0,0,0)",
|
||||
paper_bgcolor="rgba(0,0,0,0)",
|
||||
font=dict(color="white"),
|
||||
xaxis=dict(
|
||||
title="综合风险评分",
|
||||
range=[0, 105],
|
||||
gridcolor="rgba(255,255,255,0.1)",
|
||||
),
|
||||
yaxis=dict(gridcolor="rgba(255,255,255,0.1)"),
|
||||
)
|
||||
# 添加阈值线
|
||||
fig.add_vline(x=70, line_dash="dash", line_color="#F44336",
|
||||
annotation_text="高风险线(70)", annotation_position="top right")
|
||||
fig.add_vline(x=40, line_dash="dash", line_color="#FF9800",
|
||||
annotation_text="中风险线(40)", annotation_position="top right")
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
with col_right:
|
||||
st.markdown("#### 🏷️ 行业风险热力")
|
||||
|
||||
# 按行业汇总
|
||||
sector_risk = df.groupby("sector")["comprehensive_score"].mean().reset_index()
|
||||
sector_risk.columns = ["行业", "平均风险"]
|
||||
sector_risk = sector_risk.sort_values("平均风险", ascending=False)
|
||||
|
||||
fig2 = px.bar(
|
||||
sector_risk, x="行业", y="平均风险",
|
||||
color="平均风险",
|
||||
color_continuous_scale=["#4CAF50", "#FFC107", "#F44336"],
|
||||
range_color=[0, 100],
|
||||
)
|
||||
fig2.update_layout(
|
||||
height=400,
|
||||
margin=dict(l=0, r=0, t=10, b=10),
|
||||
plot_bgcolor="rgba(0,0,0,0)",
|
||||
paper_bgcolor="rgba(0,0,0,0)",
|
||||
font=dict(color="white"),
|
||||
showlegend=False,
|
||||
coloraxis_showscale=False,
|
||||
)
|
||||
st.plotly_chart(fig2, use_container_width=True)
|
||||
|
||||
# ============================================================
|
||||
# 企业列表
|
||||
# ============================================================
|
||||
st.markdown("### 📋 企业风险速览")
|
||||
|
||||
# 格式化数据表格
|
||||
display_df = df[["company", "stock_code", "industry", "comprehensive_score", "risk_level"]].copy()
|
||||
display_df.columns = ["企业名称", "股票代码", "行业", "综合风险评分", "风险等级"]
|
||||
|
||||
st.dataframe(display_df, use_container_width=True, height=400)
|
||||
|
||||
# ============================================================
|
||||
# 底部信息
|
||||
# ============================================================
|
||||
st.markdown("---")
|
||||
st.markdown("""
|
||||
<div style="text-align: center; color: #666; font-size: 0.85em;">
|
||||
<p>🛡️ 科创企业智能风控与核保系统 v1.0</p>
|
||||
<p>技术架构: Multi-Agent 交叉验证 × 供应链知识图谱 × 动态保险定价</p>
|
||||
<p>数据来源: 科创板公开年报 · BIS 实体清单 · 网信办算法备案公示</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
@@ -0,0 +1,142 @@
|
||||
# 中国青基会平安励志计划 · 2026竞赛作品
|
||||
# 《数智风控:AI 科创企业新型风险前瞻治理方案》
|
||||
## 原型系统部署与运行维护手册 (XH-202626)
|
||||
|
||||
---
|
||||
|
||||
### 一、 系统概述与体验入口
|
||||
|
||||
本系统交付了完备的金融级 Web 风控系统原型,已完成全套生产环境部署与 SSL 证书加密,支持公网在线实时访问与本地一键部署运行。
|
||||
|
||||
- **公网加密访问入口**:`https://risk.aiformat.cn`
|
||||
- **公网服务器 IP**:`159.75.81.121`
|
||||
- **技术栈规范**:
|
||||
- **核心语言**:Python 3.10+
|
||||
- **Web 交互框架**:Streamlit 1.32+
|
||||
- **大模型推理引擎**:DeepSeek API (`deepseek-chat` 深度推理底座)
|
||||
- **图谱计算引擎**:NetworkX 3.0+ (广度优先 BFS 风险传染算法)
|
||||
- **可视化组件**:Plotly 5.18+ / PyVis 0.3+
|
||||
- **反向代理与守护**:Nginx 1.18+ + Systemd 服务守护 + Let's Encrypt SSL
|
||||
|
||||
---
|
||||
|
||||
### 二、 本地快速一键运行指南
|
||||
|
||||
若评审专家或用户需要在本地环境直接运行本源码系统,请按以下步骤操作:
|
||||
|
||||
#### 1. 环境准备
|
||||
确保本地安装了 **Python 3.10** 或更高版本:
|
||||
```bash
|
||||
python --version
|
||||
```
|
||||
|
||||
#### 2. 解压与安装依赖
|
||||
解压源码包后进入项目根目录,运行依赖安装命令:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### 3. 配置 DeepSeek 大模型 API Key (可选)
|
||||
系统默认内置了 DeepSeek 演示 API 接口。如需使用自定义密钥,可打开 `config.py` 文件配置:
|
||||
```python
|
||||
DEEPSEEK_API_KEY = "your-deepseek-api-key"
|
||||
```
|
||||
|
||||
#### 4. 一键启动 Web 系统
|
||||
在终端运行以下命令启动本地交互服务:
|
||||
```bash
|
||||
streamlit run 🏠_系统首页.py
|
||||
```
|
||||
终端启动成功后,浏览器将自动弹窗并打开系统界面:
|
||||
`http://localhost:8501`
|
||||
|
||||
---
|
||||
|
||||
### 三、 服务器生产环境部署规范 (CentOS / Ubuntu)
|
||||
|
||||
本系统已在生产服务器 `159.75.81.121` 上完成了生产级部署,配置规范如下:
|
||||
|
||||
#### 1. 代码目录结构
|
||||
```text
|
||||
/opt/risk_system/
|
||||
├── 🏠_系统首页.py # 系统控制台大屏入口
|
||||
├── config.py # 全局参数与风控规则配置
|
||||
├── requirements.txt # Python 依赖清单
|
||||
├── pages/ # 4 大业务功能子模块
|
||||
│ ├── 01_📊_企业风险概览.py
|
||||
│ ├── 02_🕸️_供应链知识图谱.py
|
||||
│ ├── 03_🤖_多智能体辩论诊断.py
|
||||
│ └── 04_💰_动态定价与核保.py
|
||||
├── risk_engine/ # Multi-Agent 专家辩论与冲突平息引擎
|
||||
├── knowledge_graph/ # NetworkX 图谱拓扑与 BIS 穿透算法
|
||||
└── collectors/ # AKShare 财报与网信办/BIS 提取器
|
||||
```
|
||||
|
||||
#### 2. Systemd 服务守护配置 (`/etc/systemd/system/risk_app.service`)
|
||||
使用 Systemd 实现无人值守自动拉起与宕机自愈:
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Streamlit Risk App Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
WorkingDirectory=/opt/risk_system
|
||||
ExecStart=/usr/local/bin/streamlit run 🏠_系统首页.py --server.port 8501 --server.headless true
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
启动与状态检查命令:
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl enable risk_app
|
||||
systemctl restart risk_app
|
||||
systemctl status risk_app
|
||||
```
|
||||
|
||||
#### 3. Nginx 反向代理与 WebSocket 连通配置 (`/etc/nginx/conf.d/risk.conf`)
|
||||
配置文件支持 HTTP(80) 自动强制重定向至 HTTPS(443),并开启 WebSocket 实时推流支持:
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name risk.aiformat.cn;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name risk.aiformat.cn;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/risk.aiformat.cn/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/risk.aiformat.cn/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8501;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 四、 提交材料清单 (Checklist)
|
||||
|
||||
| 序号 | 材料名称 | 文件类型 | 描述 |
|
||||
|:---|:---|:---|:---|
|
||||
| 01 | **研究报告** | `.md` / `.pdf` | 完整的学术研究报告与六维风控理论推导 |
|
||||
| 02 | **竞赛汇报 PPT** | `.pptx` | 包含 22 页大字架构与 4 大模块实操截图的演示文稿 |
|
||||
| 03 | **原型系统完整源码** | Python 代码包 | 包含 Agent 辩论、知识图谱、动态精算与 Web 源码 |
|
||||
| 04 | **部署与运行手册** | `.md` | 本文档,提供本地运行与云服务器守护指引 |
|
||||
|
||||
---
|
||||
*中国青基会平安励志计划 · 2026 挑战杯“揭榜挂帅”擂台赛 XH-202626 课题研发团队*
|
||||
Reference in New Issue
Block a user