feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
204
web/tests/agent-release-monitor-panel.test.mjs
Normal file
204
web/tests/agent-release-monitor-panel.test.mjs
Normal file
@@ -0,0 +1,204 @@
|
||||
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]))
|
||||
}
|
||||
Reference in New Issue
Block a user