import db from '../config/database'; import { logger } from '../utils/logger'; import { DecisionExplainabilityEngine } from '../core/ai/DecisionExplainabilityEngine'; export interface SupplierRiskProfile { supplierId: string; name: string; riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; notices: string[]; lastScanAt: Date; } /** * [BIZ_OPS_157] 供应商经营风险 (法务/工商) 自动探测 (Supplier Risk Radar) * @description 核心逻辑:分析 cf_suppliers 中的经营风险字段,结合绩效数据(defect_rate),识别高危供应商。 */ export class SupplierRiskRadar { /** * 执行供应商风险扫描 */ static async scanRisks(tenantId: string): Promise { logger.info(`[SupplierRisk] Scanning risks for tenant: ${tenantId}`); try { // 1. 获取该租户下绩效异常或有风险记录的供应商 (Zero-Mock Policy) const highRiskSuppliers = await db('cf_suppliers') .where('tenant_id', tenantId) .where(function() { this.where('risk_level', '!=', 'LOW') .orWhere('defect_rate', '>', 0.05) // 质量异常 .orWhere('rating_score', '<', 60); // 评分极低 }) .select('id', 'name', 'risk_level', 'legal_notices', 'defect_rate', 'rating_score', 'updated_at'); const profiles: SupplierRiskProfile[] = []; for (const supplier of highRiskSuppliers) { const notices = supplier.legal_notices ? supplier.legal_notices.split('|') : []; let riskLevel: SupplierRiskProfile['riskLevel'] = supplier.risk_level as any; // 2. 联动分析:如果质量极差且有法务风险,升级为 CRITICAL if (supplier.defect_rate > 0.1 && riskLevel === 'HIGH') { riskLevel = 'CRITICAL'; } if (riskLevel === 'CRITICAL' || riskLevel === 'HIGH') { const reason = `Supplier ${supplier.name} identified as ${riskLevel} risk. ` + `Quality Defect Rate: ${(supplier.defect_rate * 100).toFixed(2)}%. ` + `Legal/Business Notices: ${notices.length} found.`; // 3. [UX_XAI_01] 记录决策证据链 await DecisionExplainabilityEngine.logDecision({ tenantId, module: 'SUPPLIER_RISK', resourceId: supplier.id, decisionType: 'SUPPLIER_REPLACEMENT_SUGGESTION', causalChain: reason, factors: [ { name: 'DefectRate', value: supplier.defect_rate, weight: 0.5, impact: 'NEGATIVE' }, { name: 'LegalRiskLevel', value: supplier.risk_level, weight: 0.5, impact: 'NEGATIVE' } ], traceId: `risk-radar-${Date.now()}` }); } profiles.push({ supplierId: supplier.id, name: supplier.name, riskLevel, notices, lastScanAt: supplier.updated_at }); } return profiles; } catch (err: any) { logger.error(`[SupplierRisk][FATAL] Scan failed: ${err.message}`); throw { category: 'Logic Conflict', rootCause: 'Failed to access cf_suppliers for risk radar', mitigation: 'Check cf_suppliers schema and connectivity' }; } } }