feat: 新增风险规则生成引擎与知识图谱可视化
后端新增风险规则自动生成和模板执行服务,支持从规则资产 批量生成并持久化风险规则文件;知识库入库日志增强图谱 查询和本地 RAG 回退,前端审计页面增加风险规则模型和流 程图组件,知识入库面板拆分为图谱可视化子组件,报销创 建页面增加引导式流程模型,更新知识库索引数据。
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user