/** * 统一报价 A2 · 替代选型 —— 活动包代码入口(ADR-004)。 * loop 活动:调度权归 AI;本文件只做视图流控与「差异没核清不放行」的把关呈现。 */ import { defineActivity } from '@sdk'; import type { ActivityBase, ActivityOutcome } from '@sdk'; const SHORTLIST = 'quote_alternative.shortlist'; const BENCHMARK = 'quote_alternative.benchmark'; export default defineActivity({ name: 'quote-alternative', paradigm: 'loop', async run(base: ActivityBase): Promise { const shortlist = await base.ledger.read(SHORTLIST); const benchmark = await base.ledger.read(BENCHMARK); // 差异没核清的不放行 —— 禁区的机检形态 const unclear = benchmark.filter((r) => String(r['risk'] ?? '').trim() === ''); await base.interaction.pushView([ { t: 'kv', title: '选型进展', items: [ { k: '原型号', v: String(base.contract.data['originalPn'] ?? '') }, { k: '初筛候选', v: `${shortlist.length} 颗` }, { k: '已对标', v: `${benchmark.length} 颗` }, { k: '差异未核清', v: `${unclear.length} 颗` }, ], }, { t: 'cards', title: '候选替代', items: benchmark.map((r) => ({ tag: String(r['grade'] ?? '待定级'), title: String(r['candidate_pn'] ?? ''), swap: { from: String(r['original_pn'] ?? ''), to: String(r['candidate_pn'] ?? '') }, big: String(r['grade'] ?? '—'), bigLabel: '替代等级', note: String(r['risk'] ?? '差异风险未写'), tone: String(r['risk'] ?? '').trim() === '' ? 'danger' : 'ok', })), }, ]); if (benchmark.length < 2) { await base.interaction.say( shortlist.length === 0 ? '尚未撒网,先拉初筛清单。' : `已初筛 ${shortlist.length} 颗,继续逐条核验分级。`, 'conclusion', ); return { kind: 'await', reason: '候选不足 2 颗' }; } if (unclear.length > 0) { await base.interaction.say(`${unclear.length} 颗候选的差异风险未写清 —— 差异没核清的不放行。`, 'criterion'); return { kind: 'await', reason: '差异风险未核清' }; } await base.interaction.say('候选已分级、差异与风险已写清,可签字交回发起方。', 'criterion'); return { kind: 'submit', deliverable: 'benchmark-table', rationale: '2 颗以上分级候选,差异与风险齐备' }; }, });