feat(web): 文档查询意图补充风险过滤与 X 天前范围

- aiDocumentQueryIntent 新增风险等级过滤(无/高/中/低/有风险)与 N 天前日期范围解析
- aiDocumentQueryModel/aiConversationHtmlRenderer 渲染适配风险过滤标签
- useWorkbenchAiDocumentQueryFlow/aiWorkbenchConversationStore 会话流转适配命令意图
- 更新 ai-document-query-model/ai-conversation-html-renderer/assistant-session-draft-delete 测试
This commit is contained in:
caoxiaozhu
2026-06-24 22:59:05 +08:00
parent 3eb78d343a
commit e5b03c6601
8 changed files with 517 additions and 39 deletions

View File

@@ -9,6 +9,8 @@ import {
resolveAiDocumentQueryIntent
} from '../src/utils/aiDocumentQueryModel.js'
import { renderAiConversationHtml } from '../src/utils/aiConversationHtmlRenderer.js'
import { resolveWorkbenchIntentActionRoute } from '../src/composables/workbenchAiMode/workbenchIntentActionPolicy.js'
import { resolveWorkbenchIntentFrame } from '../src/composables/workbenchAiMode/workbenchIntentFrameModel.js'
const today = '2026-06-20'
@@ -68,6 +70,21 @@ test('AI document query intent detects approval document questions', () => {
assert.equal(intent?.sourceLabel, '待我审核的单据')
})
test('AI document query intent detects short approval workbench commands', () => {
const reviewIntent = resolveAiDocumentQueryIntent('我要审核', { today })
const todoIntent = resolveAiDocumentQueryIntent('待办审批', { today })
assert.equal(reviewIntent?.source, 'approval')
assert.equal(reviewIntent?.documentType, 'all')
assert.equal(reviewIntent?.sourceLabel, '待我审核的单据')
assert.equal(todoIntent?.source, 'approval')
assert.equal(todoIntent?.sourceLabel, '待我审核的单据')
})
test('AI document query intent keeps approval policy questions out of document query', () => {
assert.equal(resolveAiDocumentQueryIntent('审批规则怎么走', { today }), null)
})
test('AI document query keeps explicit own-document scope separate from accessible documents', () => {
const intent = resolveAiDocumentQueryIntent('我名下有哪些单据?', { today })
@@ -136,6 +153,66 @@ test('AI document query combines natural-language filters', () => {
assert.match(buildAiDocumentQueryConditionSummary(intent), /金额不少于1000元/)
})
test('AI document query filters draft candidates by relative day', () => {
const intent = resolveAiDocumentQueryIntent('我的 3天前 草稿单据', { today: '2026-06-24' })
const records = filterAiDocumentQueryRecords([
{
id: 'draft-3-days-ago',
claim_no: 'AP-20260621001',
document_type_code: 'application',
status: 'draft',
reason: '三天前保存的申请草稿',
created_at: '2026-06-21T09:00:00Z'
},
{
id: 'draft-yesterday',
claim_no: 'AP-20260623001',
document_type_code: 'application',
status: 'draft',
reason: '昨天保存的申请草稿',
created_at: '2026-06-23T09:00:00Z'
}
], intent)
assert.equal(intent?.source, 'mine')
assert.equal(intent?.statusFilter?.label, '草稿')
assert.equal(intent?.timeRange?.start, '2026-06-21')
assert.equal(intent?.timeRange?.end, '2026-06-21')
assert.deepEqual(records.map((record) => record.documentNo), ['AP-20260621001'])
})
test('AI document query filters no-risk approval application candidates', () => {
const intent = resolveAiDocumentQueryIntent('待我审核 无风险 申请单', { today })
const payload = [{
id: 'approval-no-risk',
claim_no: 'AP-NORISK-001',
document_type_code: 'application',
expense_type: 'travel_application',
status: 'submitted',
reason: '合规差旅申请',
created_at: '2026-06-20T09:00:00Z',
risk_flags_json: [],
risk_summary: '无'
}, {
id: 'approval-high-risk',
claim_no: 'AP-RISK-001',
document_type_code: 'application',
expense_type: 'travel_application',
status: 'submitted',
reason: '住宿超标申请',
created_at: '2026-06-20T10:00:00Z',
risk_flags_json: [{ severity: 'high', summary: '住宿超标' }],
risk_summary: '住宿超标'
}]
const records = filterAiDocumentQueryRecords({ items: payload, querySource: 'approval' }, intent)
assert.equal(intent?.source, 'approval')
assert.equal(intent?.documentType, 'application')
assert.equal(intent?.riskFilter?.level, 'none')
assert.match(buildAiDocumentQueryConditionSummary(intent), /风险:无风险/)
assert.deepEqual(records.map((record) => record.documentNo), ['AP-NORISK-001'])
})
test('AI document query excludes undated rows when a time condition is present', () => {
const intent = resolveAiDocumentQueryIntent('我2月有哪些单据', { today })
const records = filterAiDocumentQueryRecords([
@@ -246,6 +323,28 @@ test('AI document query html cards render as trusted card markup', () => {
assert.doesNotMatch(rendered, /<blockquote>/)
})
test('AI document query keeps high-risk command guidance in trusted rendered markup', () => {
const frame = resolveWorkbenchIntentFrame('删除申请单草稿', { today: '2026-06-24' })
const route = resolveWorkbenchIntentActionRoute(frame)
const intent = resolveAiDocumentQueryIntent(route.queryPrompt, { today: '2026-06-24' })
const rendered = renderAiConversationHtml(buildAiDocumentQueryMessage(intent, [{
id: 'draft-application-1',
claim_no: 'AP-DRAFT-001',
document_type_code: 'application',
expense_type: 'travel_application',
status: 'draft',
reason: '测试申请草稿',
created_at: '2026-06-24T09:00:00Z'
}], { commandFrame: frame }))
assert.match(rendered, /<h3 class="ai-html-title">已查询到相关单据<\/h3>/)
assert.match(rendered, /<section class="ai-document-command-guidance" aria-label="高风险操作提示">/)
assert.match(rendered, /系统不会直接删除相关单据/)
assert.match(rendered, /进入单据详情核对后再操作/)
assert.match(rendered, /<section class="ai-document-card-list" aria-label="单据查询结果">/)
assert.match(rendered, /进入详情确认删除/)
})
test('AI document query trusted html rejects unsafe card markup', () => {
const rendered = renderAiConversationHtml([
'### 查询结果',