- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制 - 引入费用审批动态路由、平台风险分级、预审与风险阶段管理 - 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板 - 新增 Hermes 风险线索收集器、Agent 链路追踪中心 - 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估 - 完善报销申请快速预览、权限控制与前端测试覆盖
132 lines
3.4 KiB
JavaScript
132 lines
3.4 KiB
JavaScript
const NO_RISK_SUMMARY_VALUES = new Set(['无', '暂无异常', '无异常', '暂无风险'])
|
|
const NON_RISK_SOURCES = new Set([
|
|
'manual_approval',
|
|
'finance_approval',
|
|
'approval',
|
|
'approval_log',
|
|
'expense_claim_approval',
|
|
'expense_claim_finance_approval',
|
|
'application_detail',
|
|
'application_handoff',
|
|
'application_link',
|
|
'application_submission',
|
|
'approval_routing',
|
|
'budget_approval',
|
|
'payment',
|
|
'sla_reminder',
|
|
'reminder',
|
|
'urge'
|
|
])
|
|
const NON_RISK_EVENTS = new Set([
|
|
'expense_claim_approval',
|
|
'expense_claim_finance_approval',
|
|
'expense_claim_payment_completed',
|
|
'expense_application_submission',
|
|
'expense_application_to_reimbursement_draft',
|
|
'expense_reimbursement_application_linked',
|
|
'expense_application_budget_approval',
|
|
'sla_reminder',
|
|
'reminder',
|
|
'urge'
|
|
])
|
|
const NON_RISK_TONES = new Set(['info', 'pass', 'success', 'approved', 'ok', 'none'])
|
|
const RISK_SOURCES = new Set([
|
|
'attachment_analysis',
|
|
'submission_review',
|
|
'manual_return',
|
|
'platform_risk',
|
|
'policy_review',
|
|
'scene_policy_review'
|
|
])
|
|
|
|
function normalizeText(value) {
|
|
return String(value || '').trim()
|
|
}
|
|
|
|
function normalizeKey(value) {
|
|
return normalizeText(value).toLowerCase()
|
|
}
|
|
|
|
function isApprovalOnlyText(value) {
|
|
const text = normalizeText(value)
|
|
if (!text) {
|
|
return true
|
|
}
|
|
|
|
return (
|
|
/^(同意|通过|审批通过|审核通过|已同意|无意见)$/.test(text)
|
|
|| /已审批通过/.test(text)
|
|
|| /已完成财务审核/.test(text)
|
|
|| /进入待付款/.test(text)
|
|
|| /已确认付款/.test(text)
|
|
|| /进入归档入账/.test(text)
|
|
|| /流转至/.test(text)
|
|
)
|
|
}
|
|
|
|
export function normalizeRiskFlagTone(flag) {
|
|
if (!flag || typeof flag !== 'object') {
|
|
return normalizeText(flag) ? 'medium' : 'none'
|
|
}
|
|
|
|
const tone = normalizeKey(flag.severity || flag.tone || flag.level || flag.riskTone || flag.risk_tone)
|
|
if (['high', 'medium', 'low'].includes(tone)) {
|
|
return tone
|
|
}
|
|
if (NON_RISK_TONES.has(tone)) {
|
|
return 'none'
|
|
}
|
|
|
|
const source = normalizeKey(flag.source)
|
|
if (source === 'manual_return') {
|
|
return 'medium'
|
|
}
|
|
if (RISK_SOURCES.has(source)) {
|
|
return 'medium'
|
|
}
|
|
|
|
const riskText = normalizeText(flag.message || flag.reason || flag.summary || flag.label || flag.description || flag.title)
|
|
if (riskText && !isApprovalOnlyText(riskText)) {
|
|
return 'medium'
|
|
}
|
|
|
|
return 'none'
|
|
}
|
|
|
|
export function isActionableRiskFlag(flag) {
|
|
if (!flag || typeof flag !== 'object') {
|
|
const text = normalizeText(flag)
|
|
return Boolean(text && !isApprovalOnlyText(text))
|
|
}
|
|
|
|
const source = normalizeKey(flag.source)
|
|
const eventType = normalizeKey(flag.event_type || flag.eventType)
|
|
const actionability = normalizeKey(flag.actionability)
|
|
if (NON_RISK_SOURCES.has(source) || NON_RISK_EVENTS.has(eventType)) {
|
|
return false
|
|
}
|
|
if (actionability === 'system_trace') {
|
|
return false
|
|
}
|
|
|
|
const tone = normalizeRiskFlagTone(flag)
|
|
if (tone === 'high' || tone === 'medium' || tone === 'low') {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
export function filterActionableRiskFlags(riskFlags) {
|
|
return (Array.isArray(riskFlags) ? riskFlags : []).filter((flag) => isActionableRiskFlag(flag))
|
|
}
|
|
|
|
export function isRiskSummaryWithRisk(riskSummary) {
|
|
const summary = normalizeText(riskSummary)
|
|
if (!summary || NO_RISK_SUMMARY_VALUES.has(summary) || isApprovalOnlyText(summary)) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|