feat(ai): add tenant-safe hierarchical expense learning

This commit is contained in:
caoxiaozhu
2026-07-16 14:30:41 +08:00
parent 6bdf65bc24
commit ee88a36baf
65 changed files with 6909 additions and 232 deletions

View File

@@ -4,6 +4,7 @@ import {
AI_APPLICATION_ACTION_SAVE_DRAFT,
AI_APPLICATION_ACTION_SUBMIT,
forgetAiApplicationMemory,
normalizeAiApplicationMemory,
registerAiApplicationPreviewDecision,
resolveAiApplicationLearningReceipts,
runAiApplicationPreviewAction
@@ -148,6 +149,14 @@ async function testRegistrationUsesServerCanonicalPreview() {
status: 'active',
evidenceCount: 4,
approvedEvidenceCount: 3,
scope_type: 'department',
scope_id: 'department-shanghai-delivery',
scope_label: '上海交付部',
source: 'admin_managed_org',
priority: 200,
effective_confidence: 0.92,
conflicts: [{ scope_type: 'user', reason: 'lower_priority' }],
can_revoke: false,
message: '根据历史申请偏好填入'
}]
}
@@ -188,6 +197,14 @@ async function testRegistrationUsesServerCanonicalPreview() {
status: 'applied',
evidenceCount: 4,
approvedEvidenceCount: 3,
scopeType: 'department',
scopeId: 'department-shanghai-delivery',
scopeLabel: '上海交付部',
source: 'admin_managed_org',
priority: 200,
effectiveConfidence: 0.92,
conflicts: [{ scope_type: 'user', reason: 'lower_priority' }],
canRevoke: false,
message: '根据历史申请偏好填入'
}])
}
@@ -215,6 +232,14 @@ async function testLearningReceiptNormalizationAndForgetEndpoint() {
status: 'candidate',
evidenceCount: 2,
approvedEvidenceCount: 1,
scopeType: 'user',
scopeId: '',
scopeLabel: '个人偏好',
source: 'verified_user_history',
priority: 100,
effectiveConfidence: null,
conflicts: [],
canRevoke: true,
message: '再确认一次后可形成稳定偏好'
}])
@@ -238,6 +263,52 @@ async function testLearningReceiptNormalizationAndForgetEndpoint() {
assert.equal(capturedOptions.method, 'DELETE')
}
function testHierarchicalMemoryNormalizationKeepsCompatibilityAndPermissions() {
const enterpriseMemory = normalizeAiApplicationMemory({
memory_id: 'memory-enterprise-transport',
field_key: 'transport_mode',
value: '火车',
status: 'applied',
scope_type: 'enterprise',
scope_id: 'tenant-acme',
scope_label: '企业统一制度',
source: 'admin_managed_org',
priority: '300',
effective_confidence: '0.88',
conflicts: [{ scope_type: 'department', reason: 'lower_priority' }],
can_revoke: false
})
assert.deepEqual(enterpriseMemory, {
memoryId: 'memory-enterprise-transport',
fieldKey: 'transport_mode',
fieldLabel: '出行方式',
value: '火车',
status: 'applied',
evidenceCount: 0,
approvedEvidenceCount: 0,
scopeType: 'enterprise',
scopeId: 'tenant-acme',
scopeLabel: '企业统一制度',
source: 'admin_managed_org',
priority: 300,
effectiveConfidence: 0.88,
conflicts: [{ scope_type: 'department', reason: 'lower_priority' }],
canRevoke: false,
message: ''
})
const legacyPersonalMemory = normalizeAiApplicationMemory({
memoryId: 'memory-legacy-user',
fieldKey: 'transport_mode',
value: '飞机',
status: 'active'
})
assert.equal(legacyPersonalMemory.scopeType, 'user')
assert.equal(legacyPersonalMemory.source, 'verified_user_history')
assert.equal(legacyPersonalMemory.canRevoke, true)
assert.equal(legacyPersonalMemory.status, 'applied')
}
async function testEditDraftActionCarriesClaimAndEditableFields() {
let capturedOptions = null
@@ -327,6 +398,7 @@ async function run() {
await testSaveDraftActionUsesFastPreviewEndpoint()
await testRegistrationUsesServerCanonicalPreview()
await testLearningReceiptNormalizationAndForgetEndpoint()
testHierarchicalMemoryNormalizationKeepsCompatibilityAndPermissions()
await testEditDraftActionCarriesClaimAndEditableFields()
await testApplicationActionSourceCanBeConfiguredWithoutChangingWorkbenchDefaults()
console.log('ai-application-preview-actions tests passed')