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 "低"
|
||||
Reference in New Issue
Block a user