"""历史评审案例向量库 功能: 1. 存储历史评审案例(项目特征+评审结果+评审员行为) 2. 相似案例检索(基于向量相似度) 3. 评审员行为学习(团队集体经验,非个人预测) """ import json import os from typing import List, Dict, Optional, Tuple from datetime import datetime from pathlib import Path import logging logger = logging.getLogger(__name__) class HistoryStore: """历史评审案例存储(简化版,生产环境建议使用 Chroma/Qdrant)""" def __init__(self, data_dir: str = "./xingbang_data/history"): self.data_dir = Path(data_dir) self.data_dir.mkdir(parents=True, exist_ok=True) self.cases_file = self.data_dir / "review_cases.jsonl" # 确保文件存在 if not self.cases_file.exists(): self.cases_file.touch() def add_case(self, case_data: Dict) -> str: """添加一条历史评审案例""" case_id = f"case_{datetime.now().strftime('%Y%m%d%H%M%S')}" case_record = { "case_id": case_id, "timestamp": datetime.now().isoformat(), **case_data } # 追加写入 JSONL with open(self.cases_file, "a", encoding="utf-8") as f: f.write(json.dumps(case_record, ensure_ascii=False) + "\n") logger.info(f"案例已保存: {case_id}") return case_id def search_similar_cases( self, industry: str, credit_amount: float, risk_level: str = None, limit: int = 5 ) -> List[Dict]: """检索相似案例(简化版:基于行业+金额范围)""" if not self.cases_file.exists(): return [] similar_cases = [] with open(self.cases_file, "r", encoding="utf-8") as f: for line in f: if not line.strip(): continue try: case = json.loads(line) # 行业匹配 industry_match = industry in case.get("industry", "") or case.get("industry", "") in industry # 金额范围匹配(±30%) case_amount = case.get("credit_amount", 0) amount_match = 0.7 * credit_amount <= case_amount <= 1.3 * credit_amount # 风险等级匹配(可选) risk_match = risk_level is None or case.get("risk_level") == risk_level if industry_match and amount_match and risk_match: similar_cases.append(case) except json.JSONDecodeError: continue # 按时间倒序返回最近的案例 similar_cases.sort(key=lambda x: x.get("timestamp", ""), reverse=True) return similar_cases[:limit] def get_case_by_id(self, case_id: str) -> Optional[Dict]: """根据 ID 获取案例详情""" if not self.cases_file.exists(): return None with open(self.cases_file, "r", encoding="utf-8") as f: for line in f: if not line.strip(): continue try: case = json.loads(line) if case.get("case_id") == case_id: return case except json.JSONDecodeError: continue return None def get_team_patterns(self, industry: str = None) -> Dict: """提取团队集体经验模式(高频风险点、常见处理方式)""" if not self.cases_file.exists(): return {"high_frequency_risks": [], "common_solutions": []} risk_counter = {} solution_counter = {} with open(self.cases_file, "r", encoding="utf-8") as f: for line in f: if not line.strip(): continue try: case = json.loads(line) # 行业过滤 if industry and industry not in case.get("industry", ""): continue # 统计高频风险点 for risk in case.get("identified_risks", []): risk_key = risk.get("type", "unknown") risk_counter[risk_key] = risk_counter.get(risk_key, 0) + 1 # 统计常见解决方案 solution = case.get("solution", "") if solution: solution_counter[solution] = solution_counter.get(solution, 0) + 1 except json.JSONDecodeError: continue # 排序 high_frequency_risks = sorted( [{"risk": k, "count": v} for k, v in risk_counter.items()], key=lambda x: x["count"], reverse=True )[:10] common_solutions = sorted( [{"solution": k, "count": v} for k, v in solution_counter.items()], key=lambda x: x["count"], reverse=True )[:10] return { "high_frequency_risks": high_frequency_risks, "common_solutions": common_solutions, } def get_statistics(self) -> Dict: """获取统计数据""" if not self.cases_file.exists(): return {"total_cases": 0} total_cases = 0 industry_dist = {} approval_rate = {"approved": 0, "rejected": 0, "pending": 0} with open(self.cases_file, "r", encoding="utf-8") as f: for line in f: if not line.strip(): continue try: case = json.loads(line) total_cases += 1 # 行业分布 industry = case.get("industry", "未知") industry_dist[industry] = industry_dist.get(industry, 0) + 1 # 通过率 result = case.get("review_result", "pending") if result in approval_rate: approval_rate[result] += 1 except json.JSONDecodeError: continue return { "total_cases": total_cases, "industry_distribution": industry_dist, "approval_rate": approval_rate, } # 全局单例 _history_store: Optional[HistoryStore] = None def get_history_store() -> HistoryStore: """获取历史案例存储单例""" global _history_store if _history_store is None: _history_store = HistoryStore() return _history_store