Files
X-Financial/web/tests/agent-release-monitor-panel.test.mjs
caoxiaozhu 787bc3a481 feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
2026-07-17 14:14:08 +08:00

205 lines
8.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
import {
labelAgentAssetReleaseObservation,
normalizeAgentAssetReleaseReviewLabel,
normalizeAgentAssetReleaseReviewQueue
} from '../src/services/agentAssets.js'
import {
buildReleaseMonitorMetricCards,
buildReleaseReviewDocumentRoute
} from '../src/views/scripts/useAuditReleaseMonitor.js'
function source(path) {
return readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
}
const panel = source('../src/components/audit/AuditReleaseMonitorPanel.vue')
const detail = source('../src/components/audit/AuditJsonRiskRuleDetail.vue')
const view = source('../src/views/AuditView.vue')
const service = source('../src/services/agentAssets.js')
const composable = source('../src/views/scripts/useAuditReleaseMonitor.js')
test('盲审面板正负样本混排且不读取或暗示模型预测', () => {
assert.match(panel, /正负样本随机混排/u)
assert.match(panel, /候选与基线结论对复核人隐藏/u)
assert.match(panel, /模型结论已隐藏/u)
assert.match(panel, /存在真实风险/u)
assert.match(panel, /确认无该风险/u)
assert.doesNotMatch(panel, /candidate_hit|baseline_hit/u)
assert.doesNotMatch(service, /candidate_hit|baseline_hit/u)
assert.doesNotMatch(panel, /'confirmed'|'false_positive'/u)
assert.match(panel, /'risk_present'/u)
assert.match(panel, /'risk_absent'/u)
assert.doesNotMatch(panel, /确认命中|标记误报|待复核正例/u)
assert.match(panel, /class="review-decision"[\s\S]*class="review-decision"/u)
assert.match(panel, /:key="item\.sample_id \|\| item\.observation_id"/u)
assert.match(panel, /target="_blank"/u)
assert.match(panel, /rel="noopener noreferrer"/u)
})
test('复核队列只保留盲审字段并可从 source_document_id 打开单据', () => {
const queue = normalizeAgentAssetReleaseReviewQueue({
asset_id: 'asset-1',
release_id: 'release-1',
stage: 'shadow',
version: 'v2',
pending_total: 1,
telemetry_status: 'collecting',
items: [{
sample_id: 'sample-1',
observation_id: 'observation-1',
source_document_id: 'claim-1',
rule_code: 'RISK-001',
business_stage: 'reimbursement',
prediction_blinded: true,
reviewer_count: 1,
required_reviewers: 2,
conflicted: false,
created_at: '2026-07-17T00:00:00Z',
candidate_hit: true,
baseline_hit: false
}]
})
assert.deepEqual(Object.keys(queue.items[0]).sort(), [
'business_stage', 'conflicted', 'created_at', 'observation_id', 'prediction_blinded',
'required_reviewers', 'reviewer_count', 'rule_code', 'sample_id', 'source_document_id'
])
assert.equal(queue.items[0].prediction_blinded, true)
assert.deepEqual(buildReleaseReviewDocumentRoute(queue.items[0]), {
name: 'app-document-detail',
params: { requestId: 'claim-1' }
})
assert.equal(buildReleaseReviewDocumentRoute({ source_document_id: '' }), null)
})
test('负样本证据未就绪时召回和实际漏检不可用null 不伪装为 0', () => {
const queue = normalizeAgentAssetReleaseReviewQueue({
pending_total: 4,
metrics: {
observed_count: 20,
runtime_failure_count: 1,
runtime_failure_rate: 0.05,
negative_sample_count: 8,
negative_labeled_count: 3,
negative_pending_label_count: 5,
false_negative_count: null,
estimated_false_negative_count: null,
false_negative_upper_bound: null,
random_negative_population_count: 12,
random_negative_sample_count: 4,
random_negative_labeled_count: 1,
recall: null,
recall_lower_bound: null,
recall_confidence_level: 0.95,
recall_method: 'stratified_random_audit_wilson_upper_bound',
negative_ground_truth_status: 'insufficient_random_negative_reviews'
}
})
const cards = cardsByLabel(buildReleaseMonitorMetricCards(queue))
assert.equal(queue.metrics.false_negative_count, null)
assert.equal(cards['实际漏检'].value, '不可用')
assert.match(cards['实际漏检'].hint, /不按 0 展示/u)
assert.equal(cards['估计漏检'].value, '不可用')
assert.equal(cards['召回率'].value, '不可用')
assert.equal(cards['召回率'].hint, '随机负样本复核不足')
assert.equal(cards['负样本积压'].value, '5')
assert.equal(cards['负样本标注进度'].value, '37.5%')
assert.equal(cards['随机负样本抽检'].value, '25.0%')
})
test('真实负样本证据显示召回点估计、保守下界和漏检上界', () => {
const cards = cardsByLabel(buildReleaseMonitorMetricCards({
pending_total: 2,
metrics: {
observed_count: 100,
runtime_failure_count: 0,
runtime_failure_rate: 0,
negative_sample_count: 20,
negative_labeled_count: 18,
negative_pending_label_count: 2,
false_negative_count: 2,
estimated_false_negative_count: 2.5,
false_negative_upper_bound: 4.75,
random_negative_population_count: 40,
random_negative_sample_count: 10,
random_negative_labeled_count: 10,
recall: 0.8,
recall_lower_bound: 0.64,
recall_confidence_level: 0.95,
recall_method: 'stratified_random_audit_wilson_upper_bound',
negative_ground_truth_status: 'available_stratified_random_audit'
}
}))
assert.equal(cards['实际漏检'].value, '2')
assert.equal(cards['估计漏检'].value, '2.5')
assert.equal(cards['估计漏检'].hint, '保守上界 4.75')
assert.equal(cards['负样本标注进度'].value, '90.0%')
assert.equal(cards['负样本积压'].value, '2')
assert.equal(cards['召回率'].value, '80.0%')
assert.match(cards['召回率'].hint, /保守下界 64\.0% · 95% 置信度/u)
assert.match(cards['召回率'].hint, /分层随机盲审 Wilson 保守上界/u)
})
test('发布复核使用独立租户鉴权 API、幂等请求号和中性结果文案', () => {
assert.match(service, /\/release\/review-queue/u)
assert.match(service, /\/release\/review-queue\/\$\{observationId\}\/labels/u)
assert.match(service, /normalizeAgentAssetReleaseReviewQueue\(payload\)/u)
assert.match(composable, /requestId:\s*requestId\(assetId, observationId, label\)/u)
assert.match(composable, /Promise\.all\(\[/u)
assert.match(composable, /fetchAgentAssetReleaseState/u)
assert.match(composable, /fetchAgentAssetReleaseReviewQueue/u)
assert.match(composable, /已提交“存在真实风险”/u)
assert.match(composable, /已提交“确认无该风险”/u)
assert.equal(normalizeAgentAssetReleaseReviewLabel('risk_present'), 'risk_present')
assert.equal(normalizeAgentAssetReleaseReviewLabel('risk_absent'), 'risk_absent')
assert.throws(() => normalizeAgentAssetReleaseReviewLabel('confirmed'), /risk_present/u)
assert.throws(() => normalizeAgentAssetReleaseReviewLabel('false_positive'), /risk_absent/u)
})
test('复核服务只发送 risk_present 或 risk_absent 新语义', async () => {
const originalFetch = globalThis.fetch
const bodies = []
globalThis.fetch = async (_url, options) => {
bodies.push(JSON.parse(options.body))
return new Response(JSON.stringify({ label: options.body.includes('risk_present') ? 'risk_present' : 'risk_absent' }), {
status: 200,
headers: { 'content-type': 'application/json' }
})
}
try {
await labelAgentAssetReleaseObservation('asset-1', 'observation-1', 'risk_present', {
actor: 'auditor', requestId: 'blind-review-present'
})
await labelAgentAssetReleaseObservation('asset-1', 'observation-2', 'risk_absent', {
actor: 'auditor', requestId: 'blind-review-absent'
})
assert.throws(
() => labelAgentAssetReleaseObservation('asset-1', 'observation-3', 'confirmed'),
/risk_present/u
)
} finally {
globalThis.fetch = originalFetch
}
assert.deepEqual(bodies, [{ label: 'risk_present' }, { label: 'risk_absent' }])
})
test('规则详情和审计页复用独立发布监控组件', () => {
assert.match(detail, /<AuditReleaseMonitorPanel/u)
assert.match(detail, /@label="\(observationId, label\) => emit\('label-release'/u)
assert.match(view, /:release-queue="releaseQueue"/u)
assert.match(view, /@label-release="submitReleaseLabel"/u)
})
function cardsByLabel(cards) {
return Object.fromEntries(cards.map((card) => [card.label, card]))
}