feat: 新增风险规则生成引擎与知识图谱可视化

后端新增风险规则自动生成和模板执行服务,支持从规则资产
批量生成并持久化风险规则文件;知识库入库日志增强图谱
查询和本地 RAG 回退,前端审计页面增加风险规则模型和流
程图组件,知识入库面板拆分为图谱可视化子组件,报销创
建页面增加引导式流程模型,更新知识库索引数据。
This commit is contained in:
caoxiaozhu
2026-05-23 19:54:42 +08:00
parent 5b388d08c0
commit 575f093c74
63 changed files with 35497 additions and 1517 deletions

View File

@@ -110,14 +110,14 @@ export function buildTypeFilterOptions(rows) {
const typeMap = new Map()
for (const row of rows) {
const value = String(row?.typeCode || 'other').trim() || 'other'
const value = String(row?.archiveTypeCode || row?.typeCode || 'other').trim() || 'other'
if (!typeMap.has(value)) {
typeMap.set(value, String(row?.type || row?.typeLabel || value).trim() || value)
typeMap.set(value, String(row?.archiveType || row?.type || row?.typeLabel || value).trim() || value)
}
}
return [
{ value: ARCHIVE_FILTER_ALL, label: '全部类型' },
{ value: ARCHIVE_FILTER_ALL, label: '全部归档类型' },
...Array.from(typeMap.entries())
.sort((left, right) => left[1].localeCompare(right[1], 'zh-CN'))
.map(([value, label]) => ({ value, label }))
@@ -176,7 +176,7 @@ export function applyArchiveListFilters(rows, filters) {
}
if (filters.type && filters.type !== ARCHIVE_FILTER_ALL) {
filteredRows = filteredRows.filter((row) => String(row.typeCode || '').trim() === filters.type)
filteredRows = filteredRows.filter((row) => String(row.archiveTypeCode || row.typeCode || '').trim() === filters.type)
}
if (filters.department && filters.department !== ARCHIVE_FILTER_ALL) {
@@ -193,6 +193,7 @@ export function applyArchiveListFilters(rows, filters) {
String(row.id || '').toLowerCase().includes(keyword)
|| String(row.applicant || '').toLowerCase().includes(keyword)
|| String(row.department || '').toLowerCase().includes(keyword)
|| String(row.archiveType || '').toLowerCase().includes(keyword)
|| String(row.type || '').toLowerCase().includes(keyword)
|| String(row.amount || '').toLowerCase().includes(keyword)
|| String(row.risk || '').toLowerCase().includes(keyword)

View File

@@ -130,12 +130,14 @@ function normalizeDocument(rawDocument) {
textChars: toNumber(document.text_chars),
indexedTextChars: toNumber(document.indexed_text_chars),
sectionCount: toNumber(document.section_count || sections.length),
sections,
chunkCount: toNumber(document.chunk_count || chunks.length),
chunkIds: normalizeTextList(document.chunk_ids),
chunks,
entityCount: toNumber(document.entity_count || entities.length),
relationCount: toNumber(document.relation_count || relations.length),
entities,
entityChunks: normalizeEntityChunks(document.entity_chunks || document.entityChunks),
relations,
events: normalizeEvents(document.events)
}
@@ -165,7 +167,9 @@ function normalizeProgress(rawProgress, documents) {
function normalizeGraph(rawGraph, documents) {
const graph = asObject(rawGraph)
const fallbackEntities = dedupeTextList(documents.flatMap((item) => item.entities))
const graphEntities = normalizeEntities(graph.entities)
const fallbackEntities = dedupeEntities(documents.flatMap((item) => item.entities))
const graphRelations = normalizeRelations(graph.relations)
const fallbackRelations = dedupeRelations(documents.flatMap((item) => item.relations))
return {
chunkCount: toNumber(
@@ -177,12 +181,8 @@ function normalizeGraph(rawGraph, documents) {
relationCount: toNumber(
graph.relation_count || documents.reduce((total, item) => total + item.relationCount, 0)
),
entities: normalizeTextList(graph.entities).length
? normalizeTextList(graph.entities)
: fallbackEntities,
relations: normalizeRelations(graph.relations).length
? normalizeRelations(graph.relations)
: fallbackRelations
entities: graphEntities.length ? graphEntities : fallbackEntities,
relations: graphRelations.length ? graphRelations : fallbackRelations
}
}
@@ -195,12 +195,28 @@ function normalizeChunks(rawChunks) {
id: String(item.id || item._id || `chunk-${index + 1}`).trim(),
order: toNumber(item.order ?? item.chunk_order_index ?? index),
tokens: toNumber(item.tokens),
summary: String(item.summary || item.content || '').trim()
summary: String(item.summary || item.content || '').trim(),
excerpt: String(item.excerpt || item.content_preview || item.summary || item.content || '').trim()
}
})
.sort((left, right) => left.order - right.order)
}
function normalizeEntityChunks(rawItems) {
if (!Array.isArray(rawItems)) return []
const result = []
const seen = new Set()
for (const rawItem of rawItems) {
const item = asObject(rawItem)
const entity = String(item.entity || item.name || '').trim()
const chunkIds = normalizeTextList(item.chunk_ids || item.chunkIds)
if (!entity || !chunkIds.length || seen.has(entity)) continue
seen.add(entity)
result.push({ entity, chunkIds })
}
return result
}
function normalizeSections(rawSections) {
if (!Array.isArray(rawSections)) return []
return rawSections.map((section, index) => {
@@ -225,7 +241,8 @@ function normalizeEvents(rawEvents) {
}
function normalizeEntities(rawEntities) {
return normalizeTextList(rawEntities)
if (!Array.isArray(rawEntities)) return []
return dedupeEntities(rawEntities)
}
function normalizeRelations(rawRelations) {
@@ -236,7 +253,11 @@ function normalizeRelations(rawRelations) {
return {
source: String(item.source || item.from || '').trim(),
target: String(item.target || item.to || '').trim(),
type: String(item.type || '关联').trim()
type: String(item.type || '关联').trim(),
description: String(item.description || '').trim(),
keywords: normalizeTextList(item.keywords),
weight: toNumber(item.weight || item.confidence || 1),
properties: asObject(item.properties)
}
})
.filter((item) => item.source && item.target)
@@ -263,15 +284,22 @@ function asObject(value) {
}
function normalizeTextList(value) {
if (!Array.isArray(value)) return []
return dedupeTextList(value)
if (Array.isArray(value)) return dedupeTextList(value)
return dedupeTextList(
String(value || '')
.split('<SEP>')
.filter(Boolean)
)
}
function dedupeTextList(items) {
const result = []
const seen = new Set()
for (const item of items) {
const text = String(item || '').trim()
const text =
typeof item === 'string'
? item.trim()
: String(item?.name || item?.entity || item?.title || item?.id || '').trim()
if (!text || seen.has(text)) continue
seen.add(text)
result.push(text)
@@ -279,6 +307,43 @@ function dedupeTextList(items) {
return result
}
function dedupeEntities(items) {
const result = []
const seen = new Set()
for (const rawItem of items) {
const item = asObject(rawItem)
const name =
typeof rawItem === 'string'
? rawItem.trim()
: String(
item.name ||
item.entity ||
item.entity_id ||
item.title ||
item.id ||
''
).trim()
if (!name || seen.has(name)) continue
seen.add(name)
const description = String(item.description || '').trim()
const descriptions = normalizeTextList(item.descriptions).length
? normalizeTextList(item.descriptions)
: description
? [description]
: []
result.push({
...item,
name,
type: String(item.type || item.entity_type || item.category || item.kind || '实体').trim(),
description,
descriptions,
properties: asObject(item.properties),
labels: normalizeTextList(item.labels)
})
}
return result
}
function dedupeRelations(items) {
const result = []
const seen = new Set()
@@ -289,7 +354,7 @@ function dedupeRelations(items) {
const key = `${source}::${target}::${type}`
if (!source || !target || seen.has(key)) continue
seen.add(key)
result.push({ source, target, type })
result.push({ ...item, source, target, type })
}
return result
}