Files
makemd/server/src/services/SupplierRiskRadar.ts
wurenzhi 136c2fa579 feat: 初始化项目结构并添加核心功能模块
- 新增文档模板和导航结构
- 实现服务器基础API路由和控制器
- 添加扩展插件配置和前端框架
- 引入多租户和权限管理模块
- 集成日志和数据库配置
- 添加核心业务模型和类型定义
2026-03-17 22:07:19 +08:00

86 lines
3.2 KiB
TypeScript
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.
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'
};
}
}
}