feat: 支持评估版本隔离的报告、匹配及PDF导出功能
新增revisionId参数到路由、服务及导出接口,实现不同评估版本的隔离访问。删除已废弃的需求相关服务文件,移除取消采集的融资金额、期限和用途字段,更新相关页面提示文本。同时更新文档,说明评估版本、最新资料及产品展示规则的调整,支持前台正常业务名称显示,去除环境标签。
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { revisionParams } from './records.uts'
|
||||
import { requestJson } from '@/services/api.uts'
|
||||
import { yuanToWan } from '@/services/money.uts'
|
||||
|
||||
export type ProductView = { id: string, version: number, name: string, bank: string, type: string, demo: boolean, amount: string, rate: string, term: string, objects: string, regions: string, valid: string, materials: string, source: string, sourceRef: string, checkedOn: string, conditions: string[] }
|
||||
export type MatchReason = { id: string, text: string, evidence: string[], fields: string[], explanation: string }
|
||||
export type MatchDetail = { assessmentId: string, resultVersion: number, snapshotId: string, ruleVersion: string, orderingVersion: string, item: MatchView }
|
||||
export type MatchView = { id: string, state: string, stateLabel: string, availability: string, availabilityLabel: string, product: ProductView, positive: MatchReason[], blocking: MatchReason[], cautions: MatchReason[], aiText: string, aiStatus: string, aiCanRetry: boolean, aiGeneratedAt: string }
|
||||
export type MatchPage = { assessmentId: string, resultVersion: number, snapshotId: string, ruleVersion: string, orderingVersion: string, status: string, message: string, eligible: string, ineligible: string, unknown: string, items: MatchView[], hasMore: boolean, cursor: string }
|
||||
export type MatchDetail = { assessmentId: string, revisionId: string, resultVersion: number, snapshotId: string, ruleVersion: string, orderingVersion: string, item: MatchView }
|
||||
export type MatchView = { id: string, state: string, stateLabel: string, availability: string, availabilityLabel: string, product: ProductView, positive: MatchReason[], blocking: MatchReason[], cautions: MatchReason[], aiText: string, aiStatus: string, aiCanRetry: boolean, aiCanRequest: boolean, aiGeneratedAt: string }
|
||||
export type MatchPage = { assessmentId: string, revisionId: string, resultVersion: number, snapshotId: string, ruleVersion: string, orderingVersion: string, status: string, message: string, eligible: string, ineligible: string, unknown: string, items: MatchView[], hasMore: boolean, cursor: string }
|
||||
|
||||
function text(data: UTSJSONObject, key: string): string { return data.getString(key) ?? '' }
|
||||
export function validRecordId(value: string): boolean { return /^[1-9][0-9]{0,9}$/.test(value) }
|
||||
@@ -48,7 +49,8 @@ function productView(data: UTSJSONObject, publicMode: boolean): ProductView {
|
||||
term: data.getNumber('termMinMonths') == null || data.getNumber('termMaxMonths') == null ? '未提供' : data.getNumber('termMinMonths')!.toString() + '–' + data.getNumber('termMaxMonths')!.toString() + ' 个月',
|
||||
objects: objects.map((v: string): string => labels.getString(v) ?? v).join(' / '), regions: regions.length == 0 ? '全国' : regions.map((v: string): string => labels.getString(v) ?? v).join('、'),
|
||||
valid: text(data, 'validFrom') + ' 至 ' + text(data, 'validUntil'), materials: text(data, 'information').length > 0 ? text(data, 'information') : '未提供',
|
||||
source: text(source, 'publisher') + ' · ' + text(source, 'title'), sourceRef: text(source, 'reference'), checkedOn: text(source, 'checkedOn'), conditions }
|
||||
// 内部引用用于定位资料来源,不把测试环境标识当作面向用户的产品说明。
|
||||
source: text(source, 'publisher') + ' · ' + text(source, 'title'), sourceRef: text(source, 'reference').startsWith('demo:airz-') ? '产品规则档案' : text(source, 'reference'), checkedOn: text(source, 'checkedOn'), conditions }
|
||||
}
|
||||
function matchView(data: UTSJSONObject): MatchView {
|
||||
const evidence = data['evidence'] as UTSJSONObject[]
|
||||
@@ -72,35 +74,35 @@ function matchView(data: UTSJSONObject): MatchView {
|
||||
availabilityLabel: availability == 'available' ? '当前产品可查看' : (availability == 'unavailable' ? '当前产品已下架或不在有效期' : '当前产品状态暂无法确认'),
|
||||
product: productView(data['productSnapshot'] as UTSJSONObject, false), positive: reasons('positiveReasons'), blocking: reasons('blockingReasons'), cautions: reasons('cautions'),
|
||||
aiText: text(ai, 'status') == 'completed' ? text(ai, 'text') : (text(ai, 'status') == 'failed' ? 'AI解释暂不可用,已保留全部规则依据。' : '当前展示规则依据,尚未生成单产品AI解释。'),
|
||||
aiStatus: text(ai, 'status'), aiCanRetry: ai.getBoolean('canRetry') ?? false, aiGeneratedAt: text(ai, 'generatedAt') }
|
||||
aiStatus: text(ai, 'status'), aiCanRetry: ai.getBoolean('canRetry') ?? false, aiCanRequest: data.getBoolean('canRequestExplanation') ?? false, aiGeneratedAt: text(ai, 'generatedAt') }
|
||||
}
|
||||
export async function requestProductExplanation(id: string, productId: string, requestId: string): Promise<MatchView> {
|
||||
const data = await requestJson('/assessments/' + encodeURIComponent(id) + '/matches/' + encodeURIComponent(productId) + '/explanation', 'POST', { requestId })
|
||||
export async function requestProductExplanation(id: string, productId: string, requestId: string, revisionId: string = ''): Promise<MatchView> {
|
||||
const data = await requestJson('/assessments/' + encodeURIComponent(id) + '/matches/' + encodeURIComponent(productId) + '/explanation', 'POST', { ...revisionParams(revisionId), requestId })
|
||||
const item = matchView(data['item'] as UTSJSONObject)
|
||||
if (text(data, 'assessmentId') != id || item.id != productId) throw new Error('AI解释与本次产品不一致,请重新加载。')
|
||||
if ((revisionId.length > 0 && text(data, 'revisionId') != revisionId) || text(data, 'assessmentId') != id || item.id != productId) throw new Error('AI解释与本次产品不一致,请重新加载。')
|
||||
return item
|
||||
}
|
||||
export async function fetchMatchPage(id: string, group: string, cursor: string): Promise<MatchPage> {
|
||||
const data = await requestJson('/assessments/' + encodeURIComponent(id) + '/matches', 'GET', { eligibility: group, limit: '3', cursor })
|
||||
export async function fetchMatchPage(id: string, group: string, cursor: string, revisionId: string = ''): Promise<MatchPage> {
|
||||
const data = await requestJson('/assessments/' + encodeURIComponent(id) + '/matches', 'GET', { ...revisionParams(revisionId), eligibility: group, limit: '3', cursor })
|
||||
const matching = data['matching'] as UTSJSONObject; const status = text(matching, 'status')
|
||||
if (text(data, 'assessmentId') != id) throw new Error('匹配结果与本次评估不一致,请重新加载。')
|
||||
if ((revisionId.length > 0 && text(data, 'revisionId') != revisionId) || text(data, 'assessmentId') != id) throw new Error('匹配结果与本次评估不一致,请重新加载。')
|
||||
const reason = text(matching, 'reasonCode')
|
||||
const count = (key: string): string => { const v = matching.getNumber(key); return v == null ? '—' : v.toString() }
|
||||
const message = status == 'completed' ? '按本次评估时的产品条件判断,展示顺序不代表优劣。' : (reason == 'LEGACY_INPUT' ? '旧版报告未执行产品匹配,可重新评估获取新的结果。' : (reason == 'MATCH_RULES_UNPUBLISHED' ? '本次评估尚未启用产品匹配规则。' : '本次评估没有可用产品目录,未执行产品匹配。'))
|
||||
return { assessmentId: id, resultVersion: data.getNumber('resultVersion') ?? 0, snapshotId: text(matching, 'catalogSnapshotId'), ruleVersion: text(matching, 'ruleVersion'), orderingVersion: text(matching, 'orderingPolicyVersion'), status, message, eligible: count('candidateCount'), ineligible: count('ineligibleCount'), unknown: count('unknownCount'), items: (data['items'] as UTSJSONObject[]).map((v: UTSJSONObject): MatchView => matchView(v)), hasMore: data.getBoolean('hasMore') ?? false, cursor: text(data, 'nextCursor') }
|
||||
return { assessmentId: id, revisionId: text(data, 'revisionId'), resultVersion: data.getNumber('resultVersion') ?? 0, snapshotId: text(matching, 'catalogSnapshotId'), ruleVersion: text(matching, 'ruleVersion'), orderingVersion: text(matching, 'orderingPolicyVersion'), status, message, eligible: count('candidateCount'), ineligible: count('ineligibleCount'), unknown: count('unknownCount'), items: (data['items'] as UTSJSONObject[]).map((v: UTSJSONObject): MatchView => matchView(v)), hasMore: data.getBoolean('hasMore') ?? false, cursor: text(data, 'nextCursor') }
|
||||
}
|
||||
export async function fetchMatchDetail(id: string, productId: string): Promise<MatchView> {
|
||||
return (await fetchMatchDetailRecord(id, productId)).item
|
||||
export async function fetchMatchDetail(id: string, productId: string, revisionId: string = ''): Promise<MatchView> {
|
||||
return (await fetchMatchDetailRecord(id, productId, revisionId)).item
|
||||
}
|
||||
export async function fetchMatchDetailRecord(id: string, productId: string): Promise<MatchDetail> {
|
||||
const data = await requestJson('/assessments/' + encodeURIComponent(id) + '/matches/' + encodeURIComponent(productId), 'GET', new UTSJSONObject())
|
||||
export async function fetchMatchDetailRecord(id: string, productId: string, revisionId: string = ''): Promise<MatchDetail> {
|
||||
const data = await requestJson('/assessments/' + encodeURIComponent(id) + '/matches/' + encodeURIComponent(productId), 'GET', revisionParams(revisionId))
|
||||
const matching = data['matching'] as UTSJSONObject
|
||||
const item = matchView(data['item'] as UTSJSONObject)
|
||||
const version = data.getNumber('resultVersion') ?? 0
|
||||
const snapshotId = text(matching, 'catalogSnapshotId')
|
||||
// URL只表达选择意图;读取后再次检查归属和冻结版本,不能用公共资料补齐失败列。
|
||||
if (text(data, 'assessmentId') != id || item.id != productId || item.product.id != productId || version < 1 || snapshotId.length == 0 || text(matching, 'status') != 'completed') throw new Error('产品不属于本次冻结匹配结果,请返回重新选择。')
|
||||
return { assessmentId: id, resultVersion: version, snapshotId, ruleVersion: text(matching, 'ruleVersion'), orderingVersion: text(matching, 'orderingPolicyVersion'), item }
|
||||
if ((revisionId.length > 0 && text(data, 'revisionId') != revisionId) || text(data, 'assessmentId') != id || item.id != productId || item.product.id != productId || version < 1 || snapshotId.length == 0 || text(matching, 'status') != 'completed') throw new Error('产品不属于本次冻结匹配结果,请返回重新选择。')
|
||||
return { assessmentId: id, revisionId: text(data, 'revisionId'), resultVersion: version, snapshotId, ruleVersion: text(matching, 'ruleVersion'), orderingVersion: text(matching, 'orderingPolicyVersion'), item }
|
||||
}
|
||||
export async function fetchPublicProduct(id: string): Promise<ProductView> {
|
||||
return productView(await requestJson('/products/' + encodeURIComponent(id), 'GET', new UTSJSONObject()), true)
|
||||
|
||||
Reference in New Issue
Block a user