Files
mini-openclaw/backend/api/xingbang_unified.py
T

433 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""兴邦金租智能评审助手 - 统一API(7 个助手完整版)
基于 2026.8.18 会议痛点的完整解决方案:
1. 立项前置风控助手 🔍 - 业务经理风险自查
2. 中诚信智能填报助手 📊 - 激活十几万投资的评级模板
3. 空窗期动态监控助手 🚨 - 17天空窗期风险重扫
4. 保单条款合规核验助手 🛡️ - OCR+条款解析
5. 评审报告辅助生成助手 📄 - 看资料+写报告提效
6. 主审员智能分配助手 🎯 - 多维度负荷优化
7. AI预审过滤+加速助手 🤖 - 团队经验预判+加速
"""
import asyncio
import json
from datetime import datetime
from typing import AsyncGenerator, Dict, List, Optional
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
# 导入数据层
from data_layer.erp_connector import get_erp_connector
from data_layer.history_store import get_history_store
from data_layer.reviewer_profile import get_reviewer_store
from data_layer.team_experience import get_team_experience_store
router = APIRouter(prefix="/api/xingbang/unified", tags=["兴邦智能评审统一接口"])
# ============ Pydantic 模型 ============
class ProjectBasicInfo(BaseModel):
"""项目基础信息"""
project_id: str = ""
project_name: str
client_name: str
industry: str
credit_amount: float # 万元
lease_object: str = ""
lease_value: float = 0 # 万元
class FinancialData(BaseModel):
"""财务数据"""
registered_capital: float = 0
established_years: int = 0
asset_liability_ratio: float = 0
current_ratio: float = 0
quick_ratio: float = 0
net_profit_2024: float = 0
net_profit_2025: float = 0
revenue_growth_rate: float = 0
class CreditReport(BaseModel):
"""征信报告"""
overdue_count: int = 0
query_count_recent_6m: int = 0
credit_limit: float = 0
enforcement_records: int = 0
class InsurancePolicyData(BaseModel):
"""保单数据"""
policy_number: str
insurance_company: str
policy_type: str
coverage_amount: float
exclusions: List[str] = []
policy_pdf_path: str = ""
# ============ 助手 1:立项前置风控 ============
class Assistant1Request(BaseModel):
"""立项前置风控请求"""
project: ProjectBasicInfo
financial: FinancialData
credit: CreditReport
@router.post("/assistant1/risk-check")
async def assistant1_risk_check(request: Assistant1Request):
"""助手1:立项前置风控自查(只给风险点,不给结论)"""
async def generate():
try:
# 获取团队经验
team_exp = get_team_experience_store()
thresholds = team_exp.get_approval_thresholds()
risk_patterns = team_exp.get_risk_patterns(request.project.industry)
red_flags = team_exp.get_industry_red_flags(request.project.industry)
yield f"data: {json.dumps({'type': 'start'}, ensure_ascii=False)}\n\n"
# 生成 Markdown 报告
content = f"""# 立项前置风险自查清单
## 📋 项目基本信息
- **项目名称**:{request.project.project_name}
- **客户名称**:{request.project.client_name}
- **行业**:{request.project.industry}
- **授信金额**:{request.project.credit_amount} 万元
- **租赁物**:{request.project.lease_object or '未填写'}
- **租赁物价值**:{request.project.lease_value} 万元
---
## 🔍 风险自查结果
"""
yield f"data: {json.dumps({'type': 'content', 'data': content}, ensure_ascii=False)}\n\n"
# 检查高风险项
high_risks = []
medium_risks = []
low_risks = []
# 1. 客户资质检查
if request.financial.registered_capital < 1000:
high_risks.append("**注册资本不足**:注册资本 {:.0f} 万元 < 1000 万元(标准要求)\n - **建议**:考虑提高授信门槛或要求集团担保".format(request.financial.registered_capital))
if request.financial.established_years < 3:
medium_risks.append("**成立时间较短**:成立 {} 年 < 3 年(标准要求)\n - **建议**:补充实控人其他企业经营情况".format(request.financial.established_years))
# 2. 财务指标检查
asset_liability = request.financial.asset_liability_ratio
threshold_al = thresholds.get("资产负债率", {})
if asset_liability > threshold_al.get("critical", 0.8):
high_risks.append(f"**资产负债率过高**:{asset_liability:.1%} > {threshold_al.get('critical', 0.8):.0%}(警戒线)\n - **团队共识**:{threshold_al.get('team_consensus', '需要额外担保')}")
elif asset_liability > threshold_al.get("warning", 0.7):
medium_risks.append(f"**资产负债率偏高**:{asset_liability:.1%}(位于 {threshold_al.get('warning', 0.7):.0%}-{threshold_al.get('critical', 0.8):.0%} 区间)\n - **建议**:补充负债结构说明,关注短期偿债压力")
else:
low_risks.append(f"✅ **资产负债率**:{asset_liability:.1%}(良好)")
current_ratio = request.financial.current_ratio
threshold_cr = thresholds.get("流动比率", {})
if current_ratio < threshold_cr.get("critical", 1.0):
high_risks.append(f"**流动比率不足**:{current_ratio:.2f} < {threshold_cr.get('critical', 1.0):.2f}(红线)\n - **团队共识**:{threshold_cr.get('team_consensus', '短期偿债能力堪忧')}")
elif current_ratio < threshold_cr.get("warning", 1.2):
medium_risks.append(f"**流动比率偏低**:{current_ratio:.2f}\n - **建议**:关注应收账款周转情况")
else:
low_risks.append(f"✅ **流动比率**:{current_ratio:.2f}(符合标准)")
# 3. 征信检查
if request.credit.overdue_count > 0:
high_risks.append(f"**征信逾期记录**:近 2 年有 {request.credit.overdue_count} 次逾期\n - **建议**:要求客户提供逾期说明及还款证明")
else:
low_risks.append("✅ **征信记录**:近 2 年无逾期(良好)")
if request.credit.enforcement_records > 0:
high_risks.append(f"**被执行记录**:存在 {request.credit.enforcement_records} 条被执行记录\n - **重要**:评审大概率退回,需先解决执行案件")
else:
low_risks.append("✅ **被执行记录**:无(良好)")
if request.credit.query_count_recent_6m > 10:
medium_risks.append(f"**征信查询频繁**:近 6 个月查询 {request.credit.query_count_recent_6m} 次\n - **建议**:了解客户融资需求是否过于迫切")
# 4. 租赁物检查
if request.project.lease_value > 0:
coverage_ratio = request.project.lease_value / request.project.credit_amount
if coverage_ratio < 1.2:
high_risks.append(f"**租赁物价值不足**:租赁物价值 {request.project.lease_value} 万元,覆盖率 {coverage_ratio:.1%} < 120%(标准要求)\n - **建议**:补充其他担保措施")
else:
low_risks.append(f"✅ **租赁物覆盖率**:{coverage_ratio:.1%}(符合要求)")
# 5. 行业红线检查
if red_flags:
content_flags = f"\n### ⚠️ {request.project.industry} 行业常见风险点\n\n"
for flag in red_flags:
content_flags += f"- {flag}\n"
content_flags += "\n**建议**:核实客户是否触及以上风险点\n\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_flags}, ensure_ascii=False)}\n\n"
# 输出风险项
if high_risks:
content_high = "### 🔴 高风险项(必须处理,否则评审大概率退回)\n\n"
for i, risk in enumerate(high_risks, 1):
content_high += f"{i}. {risk}\n\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_high}, ensure_ascii=False)}\n\n"
if medium_risks:
content_medium = "### 🟡 中风险项(建议补充材料或说明)\n\n"
for i, risk in enumerate(medium_risks, 1):
content_medium += f"{i}. {risk}\n\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_medium}, ensure_ascii=False)}\n\n"
if low_risks:
content_low = "### 🟢 低风险项(符合要求)\n\n"
for risk in low_risks:
content_low += f"- {risk}\n"
content_low += "\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_low}, ensure_ascii=False)}\n\n"
# 整体评估
if len(high_risks) == 0 and len(medium_risks) == 0:
suggestion = "✅ **直接提交**:项目风险可控,可直接提交评审"
elif len(high_risks) == 0:
suggestion = "🟡 **补充材料后提交**:建议先处理中风险项,提高通过率"
else:
suggestion = "🔴 **重大调整后提交**:存在高风险项,建议先与客户沟通调整方案"
content_final = f"""---
## 💡 整体评估
{suggestion}
**说明**:本清单基于团队历史评审经验生成,仅供参考,不代表评审结论。最终是否通过由主审员独立判断。
"""
yield f"data: {json.dumps({'type': 'content', 'data': content_final}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
# ============ 助手 6:主审员智能分配 ============
class Assistant6Request(BaseModel):
"""主审员智能分配请求"""
project_industry: str
project_amount: float # 万元
project_complexity: int = Field(3, ge=1, le=5, description="项目复杂度 1-5")
@router.post("/assistant6/allocate")
async def assistant6_allocate(request: Assistant6Request):
"""助手6:主审员智能分配(多维度评分)"""
try:
reviewer_store = get_reviewer_store()
# 获取推荐列表
recommendations = reviewer_store.get_recommended_reviewers(
project_industry=request.project_industry,
project_complexity=request.project_complexity,
top_n=3
)
# 获取所有主审员当前状态
all_profiles = reviewer_store.get_all_profiles()
reviewer_status = [
{
"name": name,
"current_projects": profile["current_projects"],
"specialty": profile["specialty"],
"workload_capacity": profile["workload_capacity"],
"status": "🔴 超载" if profile["current_projects"] >= profile["workload_capacity"]
else ("🟡 偏高" if profile["current_projects"] >= profile["workload_capacity"] * 0.8 else "🟢 正常")
}
for name, profile in all_profiles.items()
]
return {
"reviewers": reviewer_status,
"recommendations": recommendations,
"project_info": {
"industry": request.project_industry,
"amount": request.project_amount,
"complexity": request.project_complexity,
},
"algorithm_version": "v1.0_multidim_scoring",
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# ============ 助手 7:AI预审过滤+加速 ============
class Assistant7Request(BaseModel):
"""AI预审请求"""
project: ProjectBasicInfo
financial: FinancialData
credit: CreditReport
@router.post("/assistant7/pre-screen")
async def assistant7_pre_screen(request: Assistant7Request):
"""助手7:AI预审过滤(基于团队经验,快速判断是否值得进入正式评审)"""
async def generate():
try:
yield f"data: {json.dumps({'type': 'start'}, ensure_ascii=False)}\n\n"
# 获取团队经验和历史案例
team_exp = get_team_experience_store()
history_store = get_history_store()
thresholds = team_exp.get_approval_thresholds()
rejection_reasons = team_exp.get_common_rejection_reasons(top_n=5)
# 相似案例检索
similar_cases = history_store.search_similar_cases(
industry=request.project.industry,
credit_amount=request.project.credit_amount,
limit=3
)
content = f"""# AI预审报告(团队经验模式)
## 📊 项目信息
- **项目名称**:{request.project.project_name}
- **客户名称**:{request.project.client_name}
- **行业**:{request.project.industry}
- **授信金额**:{request.project.credit_amount} 万元
---
## 🤖 AI预审判断(基于团队 {team_exp.get_metadata().get('total_cases_analyzed', 0)} 个历史案例)
"""
yield f"data: {json.dumps({'type': 'content', 'data': content}, ensure_ascii=False)}\n\n"
# 快速红线检查
red_line_hit = []
warning_items = []
# 1. 财务红线
if request.financial.asset_liability_ratio > thresholds.get("资产负债率", {}).get("critical", 0.8):
red_line_hit.append("资产负债率超过红线")
if request.financial.current_ratio < thresholds.get("流动比率", {}).get("critical", 1.0):
red_line_hit.append("流动比率低于红线")
# 2. 征信红线
if request.credit.enforcement_records > 0:
red_line_hit.append(f"存在 {request.credit.enforcement_records} 条被执行记录")
if request.credit.overdue_count > 2:
red_line_hit.append(f"征信逾期次数过多({request.credit.overdue_count} 次)")
# 3. 警告项
if request.financial.revenue_growth_rate < 0:
warning_items.append("营收负增长")
if request.credit.query_count_recent_6m > 10:
warning_items.append("征信查询频繁")
# AI预审结论
if red_line_hit:
conclusion = "🔴 **不建议进入正式评审**"
reason_text = "\n".join([f"- {item}" for item in red_line_hit])
content_conclusion = f"""### {conclusion}
**触及红线项**:
{reason_text}
**建议**:与客户沟通调整方案,或直接退回立项申请,避免浪费评审资源。
"""
elif len(warning_items) >= 2:
conclusion = "🟡 **建议谨慎评审**"
reason_text = "\n".join([f"- {item}" for item in warning_items])
content_conclusion = f"""### {conclusion}
**警告项**:
{reason_text}
**建议**:可以进入正式评审,但需重点关注以上风险点,建议增加尽调深度。
"""
else:
conclusion = "🟢 **建议进入正式评审**"
content_conclusion = f"""### {conclusion}
**初步判断**:项目风险可控,符合基本准入标准,建议正常推进评审流程。
"""
yield f"data: {json.dumps({'type': 'content', 'data': content_conclusion}, ensure_ascii=False)}\n\n"
# 相似案例参考
if similar_cases:
content_cases = "\n---\n\n## 📚 相似案例参考\n\n"
for i, case in enumerate(similar_cases, 1):
result_emoji = "✅" if case.get("review_result") == "approved" else "❌"
content_cases += f"""### 案例 {i}:{case.get('project_name', '未知项目')}
- **行业**:{case.get('industry', '未知')}
- **金额**:{case.get('credit_amount', 0)} 万元
- **评审结果**:{result_emoji} {case.get('review_result', '未知')}
- **关键风险点**:{', '.join([r.get('type', '未知') for r in case.get('identified_risks', [])])}
"""
yield f"data: {json.dumps({'type': 'content', 'data': content_cases}, ensure_ascii=False)}\n\n"
# 高频退回原因提示
content_tips = "\n---\n\n## 💡 团队经验提示(高频退回原因)\n\n"
for reason in rejection_reasons:
content_tips += f"- **{reason['reason']}**({reason['count']} 次) → {reason['solution']}\n"
yield f"data: {json.dumps({'type': 'content', 'data': content_tips}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'message': str(e)}, ensure_ascii=False)}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
# ============ 数据管理接口 ============
@router.get("/data/reviewers")
async def get_all_reviewers():
"""获取所有主审员画像"""
reviewer_store = get_reviewer_store()
return reviewer_store.get_all_profiles()
@router.get("/data/team-experience")
async def get_team_experience():
"""获取团队集体经验"""
team_exp = get_team_experience_store()
return {
"approval_thresholds": team_exp.get_approval_thresholds(),
"common_rejections": team_exp.get_common_rejection_reasons(),
"metadata": team_exp.get_metadata(),
}
@router.get("/data/history-stats")
async def get_history_statistics():
"""获取历史案例统计"""
history_store = get_history_store()
return history_store.get_statistics()