- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制 - 引入费用审批动态路由、平台风险分级、预审与风险阶段管理 - 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板 - 新增 Hermes 风险线索收集器、Agent 链路追踪中心 - 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估 - 完善报销申请快速预览、权限控制与前端测试覆盖
148 lines
6.9 KiB
JavaScript
148 lines
6.9 KiB
JavaScript
import { apiRequest } from './api.js'
|
|
|
|
function toNumber(value, fallback = 0) {
|
|
const number = Number(value)
|
|
return Number.isFinite(number) ? number : fallback
|
|
}
|
|
|
|
function toArray(value) {
|
|
return Array.isArray(value) ? value : []
|
|
}
|
|
|
|
function toObject(value) {
|
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
|
}
|
|
|
|
export function normalizeRiskObservation(item = {}) {
|
|
return {
|
|
id: String(item.id || '').trim(),
|
|
observationKey: String(item.observation_key || item.observationKey || '').trim(),
|
|
claimId: String(item.claim_id || item.claimId || '').trim(),
|
|
claimNo: String(item.claim_no || item.claimNo || '').trim(),
|
|
riskType: String(item.risk_type || item.riskType || '').trim(),
|
|
riskSignal: String(item.risk_signal || item.riskSignal || '').trim(),
|
|
title: String(item.title || '').trim(),
|
|
description: String(item.description || '').trim(),
|
|
riskScore: toNumber(item.risk_score ?? item.riskScore),
|
|
riskLevel: String(item.risk_level || item.riskLevel || '').trim(),
|
|
confidenceScore: toNumber(item.confidence_score ?? item.confidenceScore),
|
|
controlStage: String(item.control_stage || item.controlStage || '').trim(),
|
|
controlMode: String(item.control_mode || item.controlMode || '').trim(),
|
|
automationMode: String(item.automation_mode || item.automationMode || '').trim(),
|
|
source: String(item.source || '').trim(),
|
|
algorithmVersion: String(item.algorithm_version || item.algorithmVersion || '').trim(),
|
|
status: String(item.status || '').trim(),
|
|
feedbackStatus: String(item.feedback_status || item.feedbackStatus || '').trim(),
|
|
contributionScores: toObject(item.contribution_scores || item.contributionScores),
|
|
baseline: toObject(item.baseline),
|
|
evidence: toArray(item.evidence),
|
|
graphNodeKeys: toArray(item.graph_node_keys || item.graphNodeKeys),
|
|
graphEdgeKeys: toArray(item.graph_edge_keys || item.graphEdgeKeys),
|
|
policyRefs: toArray(item.policy_refs || item.policyRefs),
|
|
similarCaseClaimIds: toArray(item.similar_case_claim_ids || item.similarCaseClaimIds),
|
|
ontology: toObject(item.ontology_json || item.ontologyJson),
|
|
decisionTrace: toObject(item.decision_trace || item.decisionTrace),
|
|
feedbackItems: toArray(item.feedback_items || item.feedbackItems),
|
|
createdAt: String(item.created_at || item.createdAt || '').trim(),
|
|
updatedAt: String(item.updated_at || item.updatedAt || '').trim()
|
|
}
|
|
}
|
|
|
|
export function normalizeRiskObservationDashboard(payload = {}) {
|
|
return {
|
|
windowDays: toNumber(payload.window_days ?? payload.windowDays, 30),
|
|
totalObservations: toNumber(payload.total_observations ?? payload.totalObservations),
|
|
pendingCount: toNumber(payload.pending_count ?? payload.pendingCount),
|
|
riskClueCount: toNumber(payload.risk_clue_count ?? payload.riskClueCount),
|
|
highOrAboveCount: toNumber(payload.high_or_above_count ?? payload.highOrAboveCount),
|
|
confirmedCount: toNumber(payload.confirmed_count ?? payload.confirmedCount),
|
|
falsePositiveCount: toNumber(payload.false_positive_count ?? payload.falsePositiveCount),
|
|
feedbackSampleCount: toNumber(
|
|
payload.feedback_sample_count ?? payload.feedbackSampleCount
|
|
),
|
|
totalAmount: toNumber(payload.total_amount ?? payload.totalAmount),
|
|
averageScore: toNumber(payload.average_score ?? payload.averageScore),
|
|
confirmationRate: toNumber(payload.confirmation_rate ?? payload.confirmationRate),
|
|
falsePositiveRate: toNumber(payload.false_positive_rate ?? payload.falsePositiveRate),
|
|
candidateRuleCount: toNumber(payload.candidate_rule_count ?? payload.candidateRuleCount),
|
|
levelDistribution: toObject(payload.level_distribution || payload.levelDistribution),
|
|
statusDistribution: toObject(payload.status_distribution || payload.statusDistribution),
|
|
signalDistribution: toObject(payload.signal_distribution || payload.signalDistribution),
|
|
sourceDistribution: toObject(payload.source_distribution || payload.sourceDistribution),
|
|
automationDistribution: toObject(
|
|
payload.automation_distribution || payload.automationDistribution
|
|
),
|
|
departmentDistribution: toObject(
|
|
payload.department_distribution || payload.departmentDistribution
|
|
),
|
|
expenseTypeDistribution: toObject(
|
|
payload.expense_type_distribution || payload.expenseTypeDistribution
|
|
),
|
|
riskTypeDistribution: toObject(payload.risk_type_distribution || payload.riskTypeDistribution),
|
|
supplierDistribution: toObject(payload.supplier_distribution || payload.supplierDistribution),
|
|
employeeGradeDistribution: toObject(
|
|
payload.employee_grade_distribution || payload.employeeGradeDistribution
|
|
),
|
|
dailyTrend: toArray(payload.daily_trend || payload.dailyTrend),
|
|
topRiskSignals: toArray(payload.top_risk_signals || payload.topRiskSignals),
|
|
topDepartments: toArray(payload.top_departments || payload.topDepartments),
|
|
topEmployees: toArray(payload.top_employees || payload.topEmployees),
|
|
topSuppliers: toArray(payload.top_suppliers || payload.topSuppliers),
|
|
topExpenseTypes: toArray(payload.top_expense_types || payload.topExpenseTypes),
|
|
topRules: toArray(payload.top_rules || payload.topRules),
|
|
recentHighObservations: toArray(
|
|
payload.recent_high_observations || payload.recentHighObservations
|
|
).map(normalizeRiskObservation)
|
|
}
|
|
}
|
|
|
|
export async function fetchRiskObservationDashboard(options = {}) {
|
|
const windowDays = Math.max(1, Math.min(toNumber(options.windowDays || 30), 365))
|
|
const limit = Math.max(1, Math.min(toNumber(options.limit || 500), 2000))
|
|
const search = new URLSearchParams()
|
|
search.set('window_days', String(windowDays))
|
|
search.set('limit', String(limit))
|
|
|
|
const payload = await apiRequest(`/risk-observations/dashboard?${search.toString()}`, {
|
|
timeoutMs: toNumber(options.timeoutMs || 3500),
|
|
timeoutMessage: '风险看板数据加载超时,请稍后重试。'
|
|
})
|
|
|
|
return normalizeRiskObservationDashboard(payload)
|
|
}
|
|
|
|
export async function fetchClaimRiskObservations(claimId, options = {}) {
|
|
const normalizedClaimId = String(claimId || '').trim()
|
|
if (!normalizedClaimId) {
|
|
return []
|
|
}
|
|
|
|
const payload = await apiRequest(
|
|
`/risk-observations/claim/${encodeURIComponent(normalizedClaimId)}`,
|
|
{
|
|
timeoutMs: toNumber(options.timeoutMs || 3500),
|
|
timeoutMessage: '风险证据链加载超时,请稍后重试。'
|
|
}
|
|
)
|
|
|
|
return toArray(payload).map(normalizeRiskObservation)
|
|
}
|
|
|
|
export async function fetchRunRiskObservations(runId, options = {}) {
|
|
const normalizedRunId = String(runId || '').trim()
|
|
if (!normalizedRunId) {
|
|
return []
|
|
}
|
|
|
|
const search = new URLSearchParams()
|
|
search.set('run_id', normalizedRunId)
|
|
search.set('limit', String(Math.max(1, Math.min(toNumber(options.limit || 100), 200))))
|
|
|
|
const payload = await apiRequest(`/risk-observations?${search.toString()}`, {
|
|
timeoutMs: toNumber(options.timeoutMs || 3500),
|
|
timeoutMessage: '本次运行风险观察加载超时,请稍后重试。'
|
|
})
|
|
|
|
return toArray(payload.items || payload).map(normalizeRiskObservation)
|
|
}
|