Files
X-Financial/web/src/services/riskObservations.js

148 lines
6.9 KiB
JavaScript
Raw Normal View History

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)
}