feat: enable explicitly authorized human-reviewed clinical adoption
This commit is contained in:
@@ -25,6 +25,8 @@ export interface FollowupCapabilities {
|
||||
audio_verified: boolean
|
||||
preview_only?: boolean
|
||||
preview_ready?: boolean
|
||||
manual_review_enabled?: boolean
|
||||
manual_review_ready?: boolean
|
||||
can_review?: boolean
|
||||
test_scope_allowed?: boolean
|
||||
can_upload: boolean
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="helpVisible" class="help-box">
|
||||
<p v-if="previewOnly">当前仅开放测试识别与提炼,可核对并保存审核草稿;测试预览任务不提供病历、日常记录或备注的确认写入。</p>
|
||||
<p v-if="previewOnly">{{ capabilities.preview_only ? '当前仅开放测试识别与提炼,可核对并保存审核草稿;测试预览任务不提供确认写入。' : '这是历史测试任务,不写入病例;新上传的真实录音可按正常业务流程审核后确认入单。' }}</p>
|
||||
<p v-else>录音只生成建议,不会自动写入。逐项核对后选择采纳,再确认写入;日常记录按日期和时段分别保留。</p>
|
||||
<p>仅当前可见标签页每 5 秒刷新;已编辑内容不会被覆盖。后台分析不受关闭页面影响,未知结果请先核对任务或账单,勿重复上传。</p>
|
||||
<p v-if="detail">任务 #{{ detail.id }} · 版本 {{ detail.version }} · 保留至 {{ formatExpiry(detail.expires_at) }}</p>
|
||||
</div>
|
||||
<el-alert v-if="previewOnly" type="warning" :closable="false" title="仅测试识别与提炼,不写入病例" description="可保存审核草稿;预览就绪不代表完整音频或准确度验收通过,不允许确认写入病历、日常记录或备注。" show-icon />
|
||||
<el-alert v-if="previewOnly" type="warning" :closable="false" :title="capabilities.preview_only ? '仅测试识别与提炼,不写入病例' : '历史测试任务,仅供查看和审阅'" description="此任务保留测试用途。新业务录音请新建任务,人工核对后确认入单。" show-icon />
|
||||
<el-alert v-if="!audioReady" type="warning" :closable="false" :title="capabilities.preview_only ? '测试预览尚未就绪,暂不可上传或重试。已有任务可查看。' : '当前环境尚未通过音频能力验证,暂不可上传或重试。已有任务可查看。'" />
|
||||
<el-alert v-else-if="capabilities.can_upload && !capabilities.models.length" type="warning" :closable="false" title="暂无可用的分析模型,暂不可上传。已有任务可查看。" />
|
||||
<el-alert v-if="basicDirty" type="warning" :closable="false" :title="previewOnly ? '病历有未保存修改,测试预览不会覆盖这些修改。' : '病历有未保存修改,请先保存病历,再写入录音建议。'" />
|
||||
|
||||
@@ -112,13 +112,13 @@ export function followupFeatureEnabled(caps: FollowupCapabilities | null): boole
|
||||
return !!caps?.enabled && (!caps.preview_only || caps.test_scope_allowed === true)
|
||||
}
|
||||
export function followupAudioReady(caps: FollowupCapabilities | null): boolean {
|
||||
return !!caps && (caps.preview_only ? caps.preview_ready === true && caps.test_scope_allowed === true : caps.audio_verified)
|
||||
return !!caps && (caps.preview_only ? caps.preview_ready === true && caps.test_scope_allowed === true : caps.audio_verified || (caps.manual_review_enabled === true && caps.manual_review_ready === true))
|
||||
}
|
||||
export function followupCanReview(caps: FollowupCapabilities | null, previewTask = false): boolean {
|
||||
return followupFeatureEnabled(caps) && !!caps && (caps.preview_only || previewTask ? caps.can_review === true : caps.can_apply)
|
||||
}
|
||||
export function followupCanApply(caps: FollowupCapabilities | null, previewTask = false): boolean {
|
||||
return followupFeatureEnabled(caps) && !!caps && !caps.preview_only && !previewTask && caps.can_apply
|
||||
return followupFeatureEnabled(caps) && followupAudioReady(caps) && !!caps && !caps.preview_only && !previewTask && caps.can_apply
|
||||
}
|
||||
export function uploadIssue(file: Pick<File, 'size' | 'name'> | null, recordedAt: string, model: string, caps: FollowupCapabilities | null, viewOnly: boolean): string {
|
||||
if (viewOnly || !caps || !followupFeatureEnabled(caps) || !followupAudioReady(caps) || !caps.can_upload) return '当前不可上传录音'
|
||||
|
||||
@@ -1249,9 +1249,9 @@ test('preview actual component can upload, view transcript and save draft with f
|
||||
assert.match(f.panel.applyIssue.value, /测试预览/)
|
||||
f.dispose()
|
||||
const source = fs.readFileSync(path.join(componentDir, 'FollowupAudioPanel.vue'), 'utf8')
|
||||
assert.match(source, /v-if="previewOnly"[^>]+title="仅测试识别与提炼,不写入病例"/)
|
||||
assert.match(source, /v-if="previewOnly"[^>]+:title="capabilities.preview_only/)
|
||||
assert.match(source, /<el-button v-if="canApplyReview"[^>]+@click="applySelected">确认写入/)
|
||||
assert.match(source, /预览就绪不代表完整音频或准确度验收通过/)
|
||||
assert.match(source, /历史测试任务,仅供查看和审阅/)
|
||||
})
|
||||
|
||||
test('preview roles and review_refreshed drafts remain editable without can_apply and preserve dirty work', async () => {
|
||||
@@ -1442,3 +1442,20 @@ test('dirty draft is replaced by the authoritative applied result from another o
|
||||
assert.deepEqual(f.events, [['applied', 10]])
|
||||
f.dispose()
|
||||
})
|
||||
|
||||
|
||||
test('manual human-reviewed business mode exposes upload and explicit Apply without claiming model accuracy', async () => {
|
||||
const manual = { ...caps(), audio_verified: false, preview_only: false, manual_review_enabled: true, manual_review_ready: true, can_review: true }
|
||||
assert.equal(state.followupAudioReady(manual), true)
|
||||
assert.equal(state.followupCanApply(manual), true)
|
||||
assert.equal(state.followupCanApply(manual, true), false)
|
||||
assert.equal(state.followupAudioReady({ ...manual, manual_review_enabled: false }), false)
|
||||
assert.equal(state.followupCanApply({ ...manual, manual_review_ready: false }), false)
|
||||
assert.equal(state.followupAudioReady({ ...manual, manual_review_ready: undefined }), false)
|
||||
const f = setupPanel({}, { capabilities: manual }); await settle(); await f.panel.openTask(1)
|
||||
assert.equal(f.panel.canUpload.value, true)
|
||||
assert.equal(f.panel.canApplyReview.value, true)
|
||||
await f.panel.saveDraft(); assert.equal(f.calls.filter(x => x[0] === 'apply').length, 0)
|
||||
await f.panel.applySelected(); assert.equal(f.calls.filter(x => x[0] === 'apply').length, 1)
|
||||
f.dispose()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user