196 lines
7.5 KiB
Python
196 lines
7.5 KiB
Python
"""团队集体经验库
|
|||
|
|
|
||
|
|
功能:
|
||
|
|
1. 学习团队历史评审行为(聚合,非个人)
|
||
|
|
2. 提取高频风险模式
|
||
|
|
3. 为预审助手提供经验支持
|
||
|
|
"""
|
||
|
|
|
||
|
|
import json
|
||
|
|
from typing import Dict, List, Optional
|
||
|
|
from pathlib import Path
|
||
|
|
from datetime import datetime
|
||
|
|
import logging
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
class TeamExperienceStore:
|
||
|
|
"""团队集体经验存储"""
|
||
|
|
|
||
|
|
def __init__(self, data_dir: str = "./xingbang_data/team_experience"):
|
||
|
|
self.data_dir = Path(data_dir)
|
||
|
|
self.data_dir.mkdir(parents=True, exist_ok=True)
|
||
|
|
self.experience_file = self.data_dir / "team_patterns.json"
|
||
|
|
|
||
|
|
# 初始化默认经验模式
|
||
|
|
if not self.experience_file.exists():
|
||
|
|
self._init_default_patterns()
|
||
|
|
|
||
|
|
def _init_default_patterns(self):
|
||
|
|
"""初始化默认团队经验模式"""
|
||
|
|
default_patterns = {
|
||
|
|
"risk_patterns": {
|
||
|
|
"制造业": [
|
||
|
|
{
|
||
|
|
"risk_type": "应收账款集中度过高",
|
||
|
|
"frequency": 23,
|
||
|
|
"typical_threshold": "单一客户占比 > 30%",
|
||
|
|
"team_action": "要求补充第二担保人或增加保证金",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"risk_type": "原材料价格波动风险",
|
||
|
|
"frequency": 18,
|
||
|
|
"typical_threshold": "大宗商品占成本 > 50%",
|
||
|
|
"team_action": "要求提供价格锁定合同或套期保值证明",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
"新能源": [
|
||
|
|
{
|
||
|
|
"risk_type": "政策依赖度高",
|
||
|
|
"frequency": 34,
|
||
|
|
"typical_threshold": "补贴收入 > 营收 20%",
|
||
|
|
"team_action": "降低授信额度或缩短租赁期限",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"risk_type": "技术迭代淘汰风险",
|
||
|
|
"frequency": 21,
|
||
|
|
"typical_threshold": "设备技术落后 2 代以上",
|
||
|
|
"team_action": "要求租赁物残值担保或回购承诺",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
"交通运输": [
|
||
|
|
{
|
||
|
|
"risk_type": "油价波动影响",
|
||
|
|
"frequency": 15,
|
||
|
|
"typical_threshold": "燃油成本 > 营收 40%",
|
||
|
|
"team_action": "要求提供运价调整机制证明",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
"approval_thresholds": {
|
||
|
|
"资产负债率": {
|
||
|
|
"safe": 0.6,
|
||
|
|
"warning": 0.7,
|
||
|
|
"critical": 0.8,
|
||
|
|
"team_consensus": "≤60% 低风险,60-70% 中风险需补充说明,>70% 高风险需额外担保",
|
||
|
|
},
|
||
|
|
"流动比率": {
|
||
|
|
"safe": 1.5,
|
||
|
|
"warning": 1.2,
|
||
|
|
"critical": 1.0,
|
||
|
|
"team_consensus": "≥1.5 优秀,1.2-1.5 合格,<1.2 需关注短期偿债能力",
|
||
|
|
},
|
||
|
|
"净利润增长率": {
|
||
|
|
"safe": 0.15,
|
||
|
|
"warning": 0.05,
|
||
|
|
"critical": 0.0,
|
||
|
|
"team_consensus": "持续两年负增长通常不予通过",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"industry_red_flags": {
|
||
|
|
"光伏": ["产能严重过剩", "欧美双反调查", "地方政府欠补"],
|
||
|
|
"房地产": ["三道红线踩线", "土地储备不足", "回款周期超18个月"],
|
||
|
|
"餐饮": ["单店模型未跑通", "加盟商纠纷", "食品安全事故"],
|
||
|
|
},
|
||
|
|
"common_rejection_reasons": [
|
||
|
|
{"reason": "担保方资产不足", "count": 45, "solution": "要求补充第二担保人"},
|
||
|
|
{"reason": "征信查询次数过多", "count": 32, "solution": "要求客户说明查询原因"},
|
||
|
|
{"reason": "涉诉金额过大", "count": 28, "solution": "等待诉讼结果或要求提供和解协议"},
|
||
|
|
{"reason": "租赁物适格性存疑", "count": 25, "solution": "要求第三方评估报告"},
|
||
|
|
],
|
||
|
|
"metadata": {
|
||
|
|
"last_updated": datetime.now().isoformat(),
|
||
|
|
"total_cases_analyzed": 156,
|
||
|
|
"data_source": "2024-2025 年历史评审案例",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
with open(self.experience_file, "w", encoding="utf-8") as f:
|
||
|
|
json.dump(default_patterns, f, ensure_ascii=False, indent=2)
|
||
|
|
|
||
|
|
logger.info("团队经验模式初始化完成")
|
||
|
|
|
||
|
|
def get_risk_patterns(self, industry: str = None) -> List[Dict]:
|
||
|
|
"""获取风险模式(可按行业过滤)"""
|
||
|
|
with open(self.experience_file, "r", encoding="utf-8") as f:
|
||
|
|
data = json.load(f)
|
||
|
|
|
||
|
|
risk_patterns = data.get("risk_patterns", {})
|
||
|
|
|
||
|
|
if industry:
|
||
|
|
# 精确匹配或模糊匹配
|
||
|
|
for key in risk_patterns:
|
||
|
|
if industry in key or key in industry:
|
||
|
|
return risk_patterns[key]
|
||
|
|
return []
|
||
|
|
else:
|
||
|
|
# 返回所有模式
|
||
|
|
all_patterns = []
|
||
|
|
for patterns in risk_patterns.values():
|
||
|
|
all_patterns.extend(patterns)
|
||
|
|
return all_patterns
|
||
|
|
|
||
|
|
def get_approval_thresholds(self) -> Dict:
|
||
|
|
"""获取团队共识的审批阈值"""
|
||
|
|
with open(self.experience_file, "r", encoding="utf-8") as f:
|
||
|
|
data = json.load(f)
|
||
|
|
|
||
|
|
return data.get("approval_thresholds", {})
|
||
|
|
|
||
|
|
def get_industry_red_flags(self, industry: str) -> List[str]:
|
||
|
|
"""获取行业红线(团队总结的高危信号)"""
|
||
|
|
with open(self.experience_file, "r", encoding="utf-8") as f:
|
||
|
|
data = json.load(f)
|
||
|
|
|
||
|
|
industry_flags = data.get("industry_red_flags", {})
|
||
|
|
|
||
|
|
# 模糊匹配
|
||
|
|
for key in industry_flags:
|
||
|
|
if industry in key or key in industry:
|
||
|
|
return industry_flags[key]
|
||
|
|
|
||
|
|
return []
|
||
|
|
|
||
|
|
def get_common_rejection_reasons(self, top_n: int = 10) -> List[Dict]:
|
||
|
|
"""获取高频退回原因及解决方案"""
|
||
|
|
with open(self.experience_file, "r", encoding="utf-8") as f:
|
||
|
|
data = json.load(f)
|
||
|
|
|
||
|
|
reasons = data.get("common_rejection_reasons", [])
|
||
|
|
return reasons[:top_n]
|
||
|
|
|
||
|
|
def update_pattern(self, pattern_type: str, pattern_data: Dict):
|
||
|
|
"""更新经验模式(用于持续学习)"""
|
||
|
|
with open(self.experience_file, "r", encoding="utf-8") as f:
|
||
|
|
data = json.load(f)
|
||
|
|
|
||
|
|
# 更新指定类型的模式
|
||
|
|
if pattern_type in data:
|
||
|
|
data[pattern_type].update(pattern_data)
|
||
|
|
data["metadata"]["last_updated"] = datetime.now().isoformat()
|
||
|
|
|
||
|
|
with open(self.experience_file, "w", encoding="utf-8") as f:
|
||
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
|
|
|
||
|
|
logger.info(f"团队经验模式已更新: {pattern_type}")
|
||
|
|
|
||
|
|
def get_metadata(self) -> Dict:
|
||
|
|
"""获取元数据"""
|
||
|
|
with open(self.experience_file, "r", encoding="utf-8") as f:
|
||
|
|
data = json.load(f)
|
||
|
|
|
||
|
|
return data.get("metadata", {})
|
||
|
|
|
||
|
|
|
||
|
|
# 全局单例
|
||
|
|
_team_experience_store: Optional[TeamExperienceStore] = None
|
||
|
|
|
||
|
|
|
||
|
|
def get_team_experience_store() -> TeamExperienceStore:
|
||
|
|
"""获取团队经验存储单例"""
|
||
|
|
global _team_experience_store
|
||
|
|
if _team_experience_store is None:
|
||
|
|
_team_experience_store = TeamExperienceStore()
|
||
|
|
return _team_experience_store
|