86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
|
|
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<SupplierRiskProfile[]> {
|
|||
|
|
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'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|