/** * 活动包清单 —— 统一报价 A2 · 替代选型(案例活动 · MVP 简化形)。 * * 业务素材:`docs/MVP示例工作流.html` §1.5,按 2026-08-02 用户改盘的口径归一: * **A2 是复杂活动(需要大规模业务建模),MVP 期间不展开** —— 只做一张精美的 * HTML 验收报告 + 一张执行单:AI 挑出 C 类、组好清单交 FAE 在系统外选型填回, * 替代料查库内行情判稳(稳转 A / 不稳转 B),询价受理记录就此转换成 * **询价受理清单**传给下游。提交就是输出。 * * ── 这里**去掉了 `GRADE_DMN`(替代等级判定表)**(2026-08-02)──────────────── * 它声明着三个入参(pinCompatible / packageSame / paramDelta),而台账上没有任何列 * 承载它们、剧本里也没有一步调它 —— **一张没有求值器的判定表不是资产,是错觉**: * 看清单的人以为等级是算出来的,实际全程没人算过。MVP 口径下替代等级的判断 * 整个在 FAE 脑子里(系统外),系统不假装接得住;判稳转标那一步的规则 * (≤90 天且稳 → A,否则 → B)写死在剧本里、依据逐行落表,可回核。 */ import type { ActivityManifest, DmnModel } from '@contracts'; import { activityIdSchema } from '@contracts'; import { ALTERNATIVE_SKILLS } from './skills.ts'; import { ALT_ROSTER, ALT_WORKSHEET } from './views/report.ts'; import { ALTERNATIVE_PHASES, ALTERNATIVE_STEPS, CMMN_XML, NARRATIVE } from './case-model.ts'; const D = 'quote_alternative'; const ACTIVITY_ID = activityIdSchema.parse('quote-alternative'); /** MVP 口径:A2 没有判定表(见文件头注释)—— 判稳规则写死在剧本,依据逐行落表 */ export const ALT_DMN: DmnModel = { drd: [], tables: [] }; export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'A2 · 替代选型', packageVersion: '4.16.0', category: '统一报价', sdkVersion: '0.1.0-m5', checksum: 'dev-checksum-quote-alternative', }, /* ── 契约七件 ────────────────────────────────────────────────── */ contract: { activityId: ACTIVITY_ID, version: '1.0.0', input: { /* * **宽进到底:一个必填都不设**(2026-08-02 用户当场定:「活动是宽进严出, * 输入接受任何类型」「活动的输入契约是在活动内部归一+补全的」)。 * * 来路本就有两种形状:流内(上游放行载荷 —— rfqRef + records + 一堆摊平字段) * 与单飞(一段需求文字,可能连具体型号都没有)。门口拦谁都不对 —— * 归一是第一个计划项的活,缺什么在活动内问。 * * 上一版必填 `rfqRef + altRequests`:而 `alt_request` 表 2026-08-02 已在 A1 退休, * 那个键只是载荷数组通配规则下的空壳(内容与 records 完全相同)—— * 必填一个不存在的东西,单飞起单永远停在补全卡上。 */ data: { type: 'object', required: [], properties: { rfqRef: { type: 'string', title: '询价单号' }, records: { type: 'array', title: '上游受理记录(已打标)' }, subject: { type: 'string', title: '需求主题' }, body: { type: 'string', title: '需求正文' }, attachments: { type: 'array', format: 'file', title: '附件', items: { type: 'object', properties: { name: { type: 'string', title: '文件名' }, contentType: { type: 'string', title: '类型' }, size: { type: 'integer', title: '字节数' }, ref: { type: 'string', title: '存档号' }, text: { type: 'string', title: '读出来的文本' }, note: { type: 'string', title: '读取说明' }, }, }, }, }, }, messageAllowed: true, messageHint: '客户点名的替代授权、禁用品牌、场景约束等写这里', }, output: { /* * 出口载荷**从落地事实推导**:标量字段必须有台账列承载。 * `needSourcing` ← verdict.need_sourcing —— 流程包 E5(仍需寻源 → A3)读的 * 就是它;上一版声明了这个键而台账没有承载列,**E5 从来没有成立过**: * 选完型仍要寻价的单一律被默认边送去定价(2026-08-02 探查实证)。 */ mandatory: [ { name: 'roster', payload: { type: 'object', required: ['rfqRef', 'rows', 'needSourcing'], properties: { rfqRef: { type: 'string', title: '询价单号(单标识 —— 下游台账的主键)' }, rows: { type: 'array', title: '询价受理清单(C 类已转 A/B)' }, needSourcing: { type: 'boolean', title: '仍需寻源(清单里还有 B 类)' }, }, }, description: '询价受理清单(C 类已转 A/B)+ 寻源旗', }, ], /* * **两条出边 = 一个 XOR 选择组**(对齐流程包 E5/E6)。 * 每一口都写了 `when`(读基座按值现数的 tally 读数),这一单往哪走是**算出来的**: * 放行时基座按序求值首个命中即出,卡上只如实告诉人走哪一口、凭哪一条。 */ selective: [ { group: 'alt-route', ports: [ { name: 'to-sourcing', payload: { type: 'object', required: ['rfqRef', 'rows'], properties: { rfqRef: { type: 'string', title: '询价单号' }, rows: { type: 'array', title: '询价受理清单(含待补价的 B 类)' }, }, }, description: '清单里仍有 B 类 —— 去寻源补货源价格', when: 'quote_alternative_list_type_tag_b > 0', }, { name: 'to-pricing', payload: { type: 'object', required: ['rfqRef', 'rows'], properties: { rfqRef: { type: 'string', title: '询价单号' }, rows: { type: 'array', title: '询价受理清单(全 A 类)' }, }, }, description: '全 A 类:行情完备 —— 直通成本定价', when: 'quote_alternative_list_type_tag_b = 0', }, ], }, ], }, /* * 必交付物只有一件:**询价受理清单**。选型结论(verdict)是它的结论栏, * 不是第二份成果 —— `ledgers` 说清这份成果由哪几张表承载(A1 的账: * 各占一条,人在成果面上会看到两样东西)。 */ requiredDeliverables: [ { key: 'alt-roster', label: '询价受理清单(C 类已转 A/B)', ledger: `${D}.list`, /* * 验收页上让 FAE 就地改的那几列 —— 恰是**他的职权面**: * 替代料三列是他填的,现标 `type_tag` 是判稳规则出的 —— * 但算得对不对该由人说了算,他改一颗料的标就是在推翻一次判定(A1 的账: * 不在名单里的列压根不进写回载荷,人改了等于没改)。 * 去向不在其列:它是现标的函数,改标去向就跟着变。 */ editableAtRelease: ['alt_pn', 'alt_brand', 'alt_grade', 'alt_diff', 'alt_risk', 'alt_verify', 'alt_note', 'type_tag'], ledgers: [`${D}.list`, `${D}.verdict`], }, ], /* * 验收判据 —— **机检(L1/L2)只读基座数得出来的数**(A1 被违反三次的那条纪律): * `quote_alternative_list_rows` 基座数行数、`_type_tag_a/b/c` 基座按取值域现数。 * 上一版两条 L2 读的是活动自报的 verdict 列(candidate_total / undocumented_total)—— * 那不是门,是「改哪个数能过」的提示。 */ acceptanceCriteria: [ { key: 'roster-complete', label: '清单齐:上游受理记录逐条承继,说了几条清单里就躺着几行', level: 'L1', predicate: 'alt_line_total > 0 and quote_alternative_list_rows = alt_line_total', }, { key: 'no-c-left', label: '选型办完:每颗待选型的 C 类料都已填回替代并转成 A 或 B,或经确认剔除', level: 'L1', predicate: 'quote_alternative_list_rows > 0 and quote_alternative_list_type_tag_c = 0', }, { key: 'tag-conserved', label: '定标守恒:清单每一行都有现标,A 类加 B 类恰等于总行数', level: 'L2', predicate: 'quote_alternative_list_type_tag_a + quote_alternative_list_type_tag_b = quote_alternative_list_rows', }, /* * 来源可溯是**裁量**(L3):替代凭什么、说明留没留,机器数不出来 —— * 别假装机器判得了。裁成不满足照样能提交,理由连着卡交给签字的人。 * 这句 label 与剧本里 judge 的 criterion **必须逐字一致**(judge 按整句对名)。 */ { key: 'alt-traceable', label: '替代料来源可溯且选型说明已留:以原厂官网规格书或客户预授权为准,每颗替代写明凭什么', level: 'L3', }, /* L5 是责任关:那张卡本身就是这道关,卡面上不摆它(基座统一滤) */ { key: 'fae-release', label: 'FAE 放行:签下去这份清单即转正并流向下游', level: 'L5' }, ], /* 去向不给人改(现标的函数);人在验收页改的是成果本身(editableAtRelease) */ revisableAtRelease: [], prohibitions: [ { key: 'no-fake-alt', statement: '没有把握的替代不填:宁可在选型备注写明替代未果,也不编一个候选顶上' }, { key: 'no-banned', statement: '客户点名禁用的品牌与型号不得作替代' }, { key: 'no-tag-fudge', statement: '不许为过判据改标:现标只能来自判稳规则,或人在件上的亲手修改' }, ], authorization: { discretion: [ { key: 'widen-search', statement: 'FAE 可放宽参数或换角度重新选型;是否接受非原厂品牌、参数降级必须问客户,不得替客户决定' }, ], /* 空着不是省事:这个字段全仓没有一个消费者(债 ⑨),写了也不会被执行 */ preauthorizedActions: [], /* 一颗芯片一张选型单(按顺序派)—— 打扰上限按料数给:MVP 案例 ≤3 颗 + 放行 + 余量 */ humanCallBudget: 6, }, resources: { tools: [{ name: 'crm.history-price-read' }], skills: ALTERNATIVE_SKILLS.map((s) => s.id), models: ['deepseek-v4-flash'], roles: ['FAE'], /* * FAE 在系统外选型,回来才继续 —— activeMinutes 给足一个工作日的等待; * token 按实测估:每颗料一张卡各占两三拍,常路(1-2 颗)8-12 拍,留一倍余量。 */ budget: { steps: 60, tokens: 300_000, activeMinutes: 480, humanCalls: 6 }, }, }, /* ── 声明 ────────────────────────────────────────────────────── */ declarations: { models: ['deepseek-v4-flash'], tools: [{ name: 'crm.history-price-read' }], ledger: [ /* * ── 这里**去掉了 `shortlist` / `benchmark` / `request` 三张表**(2026-08-02)── * * MVP 口径下初筛清单与对标表都不在系统内产出 —— 选型的实质工作在 FAE * 系统外,系统收的是**结论**(替代料是哪颗、凭什么),落在清单行的三列上。 * 撤掉声明不 drop 物理表(物化只增不破坏),历史行还在库里。 */ { domain: D, object: 'list', kind: 'record', caseKey: 'rfq_ref', label: '询价受理清单', fields: [ { name: 'rfq_ref', type: 'text', primaryKey: true, masked: false, label: '询价单号' }, { name: 'pn', type: 'text', primaryKey: true, masked: false, label: 'PN' }, { name: 'customer_pn', type: 'text', primaryKey: false, masked: false, label: '客户料号' }, { name: 'category', type: 'text', primaryKey: false, masked: false, label: '品类' }, { name: 'brand', type: 'text', primaryKey: false, masked: false, label: '制造商' }, { name: 'eau', type: 'integer', primaryKey: false, masked: false, label: '年用量 EAU' }, { name: 'target_price', type: 'number', primaryKey: false, masked: false, label: '目标价' }, { name: 'lead_time', type: 'text', primaryKey: false, masked: false, label: '交期' }, /* 客户备注承继自上游 —— 预授权替代、认证要求、禁用约束都在这一格,是选型的第一手依据 */ { name: 'remark', type: 'text', primaryKey: false, masked: false, label: '客户备注(约束/预授权)' }, /* * **原标与现标两列同名不同物**:orig_type 是上游受理时的三分类(留痕,本活动不改); * type_tag 是本活动的产物(C 类判稳后转 A/B)。两列都声明取值域, * 条数由基座按值现数 —— 判据、分布条、出口谓词读的全是基座读数。 */ { name: 'orig_type', type: 'text', primaryKey: false, masked: false, label: '原标(上游三分类)', tally: ['A', 'B', 'C'] }, /* * **定案三格是待选型行的必填**(2026-08-02 用户:「工作区的 HTML 必填项 * 必须填写了才能提交啊,不填写能算任务完成了吗?」)。 * `requiredWhen` 把必填钉在 orig_type=C 的行(一张执行单、件里一颗一页, * 每页的定案三格都得齐);A/B 行原样承继,不拦。 * 「替代未果」不走「留空交回」—— 那在闸上就是空手交卷: * 未果 = 在选型备注写明缘由后交回,判稳那一步按剔除处置(剧本 §S3)。 */ { name: 'alt_pn', type: 'text', primaryKey: false, masked: false, label: '替代料 PN', requiredWhen: { column: 'orig_type', value: 'C' } }, { name: 'alt_brand', type: 'text', primaryKey: false, masked: false, label: '替代料品牌', requiredWhen: { column: 'orig_type', value: 'C' } }, /* * **替代等级与对标三列**(2026-08-02 用户指出「执行单完全是验收单了」后补齐): * 选型的定案不只是一个型号 —— FAE 的专业产出是**等级 + 差异 + 风险 + 验证建议** * (照 MVP 素材对标表的口径)。等级用三个机器码(取值域声明,基座按值现数): * P2P = 同封装同引脚可直换(核对五项:封装尺寸/引脚定义/电气/温度等级/认证); * FUNC = 功能兼容(需外围电路或 layout 调整); * OTHER = 其他(部分兼容,须打样验证)。 * 人读名由模版样式表翻译(徽上色 + 图例),FAE 在卡与件上照码填。 */ { name: 'alt_grade', type: 'text', primaryKey: false, masked: false, label: '替代等级(P2P/FUNC/OTHER)', tally: ['P2P', 'FUNC', 'OTHER'], requiredWhen: { column: 'orig_type', value: 'C' } }, { name: 'alt_diff', type: 'text', primaryKey: false, masked: false, label: '参数差异项', expect: true }, { name: 'alt_risk', type: 'text', primaryKey: false, masked: false, label: '差异风险与改动评估', expect: true }, { name: 'alt_verify', type: 'text', primaryKey: false, masked: false, label: '验证建议', expect: true }, /* * 选型备注是**我方写的注**,不标 expect —— 标了 `_gaps` 恒大于 0, * 那条门就废了(A1 的 remark 同款教训)。 */ { name: 'alt_note', type: 'text', primaryKey: false, masked: false, label: '选型备注(来源/预授权)' }, { name: 'type_tag', type: 'text', primaryKey: false, masked: false, label: '现标', tally: ['A', 'B', 'C'] }, { name: 'basis', type: 'text', primaryKey: false, masked: false, label: '转标依据' }, { name: 'history_days', type: 'integer', primaryKey: false, masked: false, label: '替代料距上次报价天数' }, /* * **选型排程**(2026-08-02 用户定:「多个芯片按顺序派选型单」)—— * 一颗芯片一张选型单,按序派:WAIT 排队 / NOW 当前在办 / DONE 已定案。 * 只有待选型的料写这一列(A/B 行留空,读数自然为 0);工作单照它组画面 * (队列丸、当前颗的档案、填回表只显当前行),判断全在样式表。 * 它是排程不是编辑面 —— 不进 data-col,人改不到。 */ ], }, { /* * 选型结论 —— **一单一行**:汇总数(报告要素卡与哨兵读)、 * 去向旗(流程包 E5 唯一读的东西)、一句人话结论。 */ domain: D, object: 'verdict', kind: 'record', caseKey: 'rfq_ref', label: '选型结论', fields: [ { name: 'rfq_ref', type: 'text', primaryKey: true, masked: false, label: '询价单号' }, { name: 'customer_code', type: 'text', primaryKey: false, masked: false, label: '客户码' }, { name: 'customer_name', type: 'text', primaryKey: false, masked: false, label: '客户名称' }, { name: 'alt_line_total', type: 'integer', primaryKey: false, masked: false, label: '清单条目数' }, { name: 'c_total', type: 'integer', primaryKey: false, masked: false, label: '待选型条数' }, /* 「FAE 的答复处理过了」的凭据 —— 只在人环走完才产生(哨兵按它放行下一步) */ { name: 'filled_total', type: 'integer', primaryKey: false, masked: false, label: '已填回替代料条数' }, { name: 'converted_a_total', type: 'integer', primaryKey: false, masked: false, label: '判稳转 A 条数' }, { name: 'converted_b_total', type: 'integer', primaryKey: false, masked: false, label: '转 B(需寻价)条数' }, { name: 'skipped_note', type: 'text', primaryKey: false, masked: false, label: '剔除了哪几颗·为什么' }, /* 流程包 E5(仍需寻源 → A3)读的就是它 —— 没有这一列,E5 永远判假(实证过) */ { name: 'need_sourcing', type: 'boolean', primaryKey: false, masked: false, label: '仍需寻源' }, /* 去向定过了没有 —— 旗是布尔,分不清「没写」与「写了 false」,另记一个计数 */ { name: 'route_decided_total', type: 'integer', primaryKey: false, masked: false, label: '已定去向' }, { name: 'summary', type: 'text', primaryKey: false, masked: false, label: '选型结论' }, ], }, ], listeners: [ { /** 时限过半:给 FAE 与负责人提个醒 —— 静态监听,时限必配 */ origin: 'static', eventType: 'time', keyFilter: { kind: 'half-deadline' }, semantic: 'observe', deadline: 'P2D', escalation: ['nudge'], }, { /** 客户改需求(应用条件/参数变更):记一笔观察,回炉与否由 Agent 裁量 */ origin: 'static', eventType: 'human', keyFilter: { kind: 'requirement-change' }, semantic: 'observe', deadline: 'P5D', escalation: ['nudge', 'reroute'], }, ], harness: { memoryWindow: 24, // FAE 在系统外干活,等他不算停滞 —— 停滞检测放宽到一天 stallAfter: 'P1D', nudge: [{ after: 'PT12H', action: 'nudge' }], retry: { times: 2, backoff: 'PT5M' }, }, }, /* ── 资产初始版 ──────────────────────────────────────────────── */ assets: { businessModel: { cmmnXml: CMMN_XML, narrative: NARRATIVE, dmn: ALT_DMN, steps: ALTERNATIVE_STEPS, phases: ALTERNATIVE_PHASES, }, skills: ALTERNATIVE_SKILLS, /* * 两个局面各一张(2026-08-02 用户指出「执行单完全是验收单了」): * · `alt-worksheet` 替代选型工作单 —— 归一与 FAE 选型期间摆它:逐颗料的选型档案、 * 替代等级判据、pin-to-pin 核对要点、三层筛指引 + 填回工作表。**这是干活的件**; * 同一局面在办与归档同一张;局面变了换张(A1 三张同款口径)。 */ viewTemplates: [ /* * **每份件两态 1:1**(2026-08-02/03 用户两次校准):在办可编辑 ↔ 办结快照 * 静态归档(finalHtml),这是「1:1」的落点;而**选型作业与收口验收各有各的件**。 */ { name: 'alt-worksheet', html: ALT_WORKSHEET }, /* * 收口件(2026-08-03 用户:「验收工单怎么又变成选型了?」)—— 选型作业与 * 收口验收是两件工作,各有各的件:One Pager 归选型执行单,**整合清单归验收** * (「清单是所有选型完成后收口才整合的东西」)。「1:1」落在每份件与它的 * 办结快照之间(finalHtml),不是全程只许一张。 */ { name: 'alt-roster', html: ALT_ROSTER }, ], cardSchemas: {}, distillHooks: [ { on: 'terminal', prompt: '归纳本单替代选型的经验:哪类料找得到替代、FAE 的选型依据形态、判稳转标的分布。', }, ], }, /* ── 样例集 —— 三条形状真不同(常备案例即验收案例)───────────── */ samples: [ { /* * ① 流内常路:上游放行载荷的**真实形状** —— records 里同一颗料出现两次 * (受理记录行 + 分类表行)、还混着结论行;顶层带摊平字段。 * 替代料(客户预授权的 PBY)库内无历史 → 判稳转 B → to-sourcing(E5 复活的那条路)。 */ name: '流内常路 · Itelma 两颗 C 类(一颗一张选型单按序派 · 替代料无历史 → 转 B 去寻源)', input: { rfqRef: 'RFQ MD14.1', customer_code: 'CU-0142', customer_name: 'Itelma', type_c_total: 2, records: [ { rfq_ref: 'RFQ MD14.1', pn: 'BBPY00201209601Y00', customer_pn: '4-14-01502', category: '滤波器', brand: 'YAGEO', eau: 30000, remark: '客户预授权替代:PBY201209T-601Y-N(BOM Analog 列)', type_tag: 'C', }, { rfq_ref: 'RFQ MD14.1', pn: 'BBPY00201209601Y00', history_days: 47, market_stable: true, lifecycle: '要求替代', type_tag: 'C', basis: 'EOL/缺货/要求替代 → C', }, { rfq_ref: 'RFQ MD14.1', pn: 'DAG2400L321504XZB01-R0', customer_pn: '9-00-00251', category: '天线', brand: '浙江嘉康', eau: 30000, remark: '', type_tag: 'C', }, { rfq_ref: 'RFQ MD14.1', pn: 'DAG2400L321504XZB01-R0', history_days: 9999, market_stable: false, lifecycle: 'EOL', type_tag: 'C', basis: 'EOL/缺货/要求替代 → C', }, { rfq_ref: 'RFQ MD14.1', pn: 'CJTB0101R6N', customer_pn: '4-00-04102', category: '集成电路', brand: 'Jiangsu Changjing', eau: 60000, type_tag: 'A', }, { rfq_ref: 'RFQ MD14.1', line_total: 3, type_a_total: 1, type_b_total: 0, type_c_total: 2, need_alternative: true, need_sourcing: false, customer_name: 'Itelma', }, ], }, expect: { ports: ['roster'] }, }, { /* * ② 客户点名替代、替代料**库内行情稳** → 判稳转 A → 全 A 直通定价(to-pricing)。 * FAE 若填回 CJTB0101R6N(库内 43 天·行情稳),就走这条路。 */ name: '点名替代 · 替代料库内行情稳(转 A 直通定价)', input: { rfqRef: 'RFQ-2026-0777', customer_name: '青岛海洋电子', records: [ { rfq_ref: 'RFQ-2026-0777', pn: 'TL072CDR', category: '运放', brand: 'TI', eau: 3000, target_price: 22, remark: '客户点名替代:国产运放优先', type_tag: 'C', }, { rfq_ref: 'RFQ-2026-0777', pn: 'TL072CDR', history_days: 9999, market_stable: false, lifecycle: '要求替代', type_tag: 'C', basis: '客户要求替代 → C', }, ], }, expect: { ports: ['roster'] }, }, { /* * ③ 单飞宽进:没有单号、没有上游清单,只有一段需求(类型 + 参数 + 场景)—— * 归一造案号、需求代号,全标 C,照常走完(输入契约在活动内归一+补全)。 */ name: '单飞需求 · 只有类型与参数(无单号无清单,宽进归一)', input: { subject: '车规运放选型需求', body: '需要一颗车规级双通道运放:供电 3-36V,AEC-Q100,替代 TL072 系列,年用量约 5K。请选型给替代建议;定案后要给采购去问价。', }, expect: { ports: ['roster'] }, }, ], entry: { file: 'index.js', checksum: 'dev-entry-quote-alternative' }, };