/** * 活动包清单 —— 统一报价 A3 · 寻源询价(案例活动 · MVP 简化形)。 * * 业务素材:`docs/MVP示例工作流.html` §1.7,按 2026-08-02 用户改盘的口径归一: * **A3 与 A2 同为复杂活动(需要大规模业务建模),MVP 期间不展开** —— 只做一张精美的 * HTML 验收报告 + 一张执行单:AI 归一目标报价清单、A 类行按库内历史价预填基准, * 采购(PRM)在系统外询价、把 B 类行的货源价格填回来,汇稿定盘后放行 —— * **生成最终的目标报价清单**交给成本定价。提交就是输出。 * * ── 这里**去掉了 `STRATEGY_DMN`(采用策略判定表)**(2026-08-02)──────────────── * 它声明着 scenario / priceDrift / quoteCount 三个入参,台账上没有任何列承载、 * 剧本里也没有一步调它 —— 一张没有求值器的判定表不是资产,是错觉。 * 四种场景(BOM早期/后期/现货/样品)的尺度之分是 PRM 系统外的判断, * MVP 口径下系统不假装接得住。 */ import type { ActivityManifest, DmnModel } from '@contracts'; import { activityIdSchema } from '@contracts'; import { SOURCING_SKILLS } from './skills.ts'; import { SOURCING_ROSTER, SOURCING_WORKSHEET } from './views/report.ts'; import { CMMN_XML, NARRATIVE, SOURCING_PHASES, SOURCING_STEPS } from './case-model.ts'; const D = 'quote_sourcing'; const ACTIVITY_ID = activityIdSchema.parse('quote-sourcing'); /** MVP 口径:A3 没有判定表(见文件头注释)—— 取价规则写死在剧本,价源逐行落表 */ export const SOURCING_DMN: DmnModel = { drd: [], tables: [] }; export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'A3 · 寻源询价', packageVersion: '3.7.0', category: '统一报价', sdkVersion: '0.1.0-m5', checksum: 'dev-checksum-quote-sourcing', }, /* ── 契约七件 ────────────────────────────────────────────────── */ contract: { activityId: ACTIVITY_ID, version: '1.0.0', input: { /* * **宽进到底:一个必填都不设**(2026-08-02 用户定:宽进严出, * 输入契约在活动内部归一 + 补全)。 * * 来路三种形状:A2 转来(rows)、A1 直达(records)、单飞(一段文字)。 * 上一版必填 `rfqRef + pn + scenario` 是**单料形状** —— 上游发的是整份清单, * 门口拦的恰是主路。 */ data: { type: 'object', required: [], properties: { rfqRef: { type: 'string', title: '询价单号' }, rows: { type: 'array', 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: '急料、场景(BOM早期/后期/现货/样品)、客户特殊要求写这里', }, output: { /* * 只有一个出口:目标报价清单交成本定价(流程包 E7 是无条件边)。 * 没有选择组 —— 没有去向要选,定盘就是把账写齐。 */ mandatory: [ { name: 'quotation', payload: { type: 'object', required: ['rfqRef', 'rows'], properties: { rfqRef: { type: 'string', title: '询价单号(单标识 —— 下游台账的主键)' }, rows: { type: 'array', title: '目标报价清单(每行带基准价与价源)' }, }, }, description: '目标报价清单(每行带基准价与价源)', }, ], selective: [], }, /* * 必交付物只有一件:**目标报价清单**。寻源结论(verdict)是它的结论栏, * `ledgers` 说清这份成果由哪几张表承载。 */ requiredDeliverables: [ { key: 'quotation-roster', label: '目标报价清单(每行带基准价)', ledger: `${D}.quote_list`, /* 验收页上让 PRM 就地改的那几列 —— 恰是他的职权面(价格五列) */ editableAtRelease: ['supplier', 'base_price', 'moq', 'source_lead_time', 'note'], ledgers: [`${D}.quote_list`, `${D}.verdict`], }, ], /* * 验收判据 —— **机检(L1/L2)只读基座数得出来的数**: * `_rows` 基座数行数、`_missing` 基座按 required 现数(与执行卡提交闸同一个数 * 同一套算法)、`_price_basis_hist/_sourced` 基座按值现数。 * 上一版两条 L2 读活动自报的 verdict 列(unquoted_total / supplier_total)—— * 那不是门,是「改哪个数能过」的提示。 */ acceptanceCriteria: [ { key: 'roster-complete', label: '清单齐:上游清单逐条承继,说了几条清单里就躺着几行', level: 'L1', predicate: 'quote_line_total > 0 and quote_sourcing_quote_list_rows = quote_line_total', }, { key: 'price-complete', label: '每行有基准价:A 类历史价、B 类货源价,缺一行放不了', level: 'L1', predicate: 'quote_sourcing_quote_list_rows > 0 and quote_sourcing_quote_list_missing = 0', }, { key: 'basis-conserved', label: '价源守恒:历史价条数加货源价条数恰等于总行数,逐行留徽可溯', level: 'L2', predicate: 'quote_sourcing_quote_list_price_basis_hist + quote_sourcing_quote_list_price_basis_sourced = quote_sourcing_quote_list_rows', }, /* * 来源可核是**裁量**(L3):供应商靠不靠谱、价问没问真,机器数不出来。 * 这句 label 与剧本里 judge 的 criterion **必须逐字一致**。 */ { key: 'source-verifiable', label: '货源与价格来源可核:B 类行的供应商与底价来自真实询价或可靠渠道,PRM 对报价负责', level: 'L3', }, /* L5 是责任关:那张卡本身就是这道关,卡面上不摆它(基座统一滤) */ { key: 'prm-release', label: 'PRM 放行:签下去这份清单即定盘并交成本定价', level: 'L5' }, ], revisableAtRelease: [], prohibitions: [ { key: 'no-made-up-price', statement: '问不到的价不许编:要么剔出本轮,要么按内部估价填并在备注写明' }, { key: 'no-stockless-trader', statement: '现货场景不得选无库存转单商' }, ], authorization: { discretion: [ { key: 'channel-band', statement: 'PRM 按场景放宽或收紧渠道尺度;贸易商尽量不用' }, ], /* * 空着不是省事:这个字段全仓没有一个消费者(债 ⑨)。 * 上一版这里写着 `mail.send` 的预授权 —— 写了也不会被执行,一并撤掉。 */ preauthorizedActions: [], humanCallBudget: 4, }, resources: { /* * 只留历史价一件工具(A 类预填的取数口)。上一版还声明 `mail.send` —— * MVP 口径下发询在 PRM 系统外,系统不代发,声明它只是白占白名单。 */ tools: [{ name: 'crm.history-price-read' }], skills: SOURCING_SKILLS.map((s) => s.id), models: ['deepseek-v4-flash'], roles: ['PRM'], /* PRM 系统外询价,按场景时限 BOM后期 3~9 日 —— activeMinutes 给足等待 */ budget: { steps: 50, tokens: 250_000, activeMinutes: 480, humanCalls: 4 }, }, }, /* ── 声明 ────────────────────────────────────────────────────── */ declarations: { models: ['deepseek-v4-flash'], tools: [{ name: 'crm.history-price-read' }], ledger: [ /* * ── 这里**去掉了 `supplier_quote` / `compare_table` 两张表**(2026-08-02)── * * MVP 口径下逐家回价与横比都不在系统内产出 —— 询价的实质工作在 PRM 系统外, * 系统收的是**结论**(定盘的供应商与底价),落在清单行的价格五列上。 * 撤掉声明不 drop 物理表(物化只增不破坏),历史行还在库里。 */ { domain: D, object: 'quote_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: '客户备注(约束/急料)' }, /* 选型转来的行带替代料 —— 取价对象是它;受理直达与单飞的行此列空着 */ { name: 'alt_pn', type: 'text', primaryKey: false, masked: false, label: '替代料 PN' }, { name: 'type_tag', type: 'text', primaryKey: false, masked: false, label: '类别(承继)', tally: ['A', 'B', 'C'] }, { name: 'supplier', type: 'text', primaryKey: false, masked: false, label: '供应商' }, /* * **底价是必填** —— 标了 `required`,基座就数得出「缺基准价的有几行」 * (读数 `quote_sourcing_quote_list_missing` / `_missing_keys`)。 * 这个数同时喂三处:件上的缺价红带、执行卡的提交闸、放行门 L1 —— * 同一个数、同一套算法,三处永远说同一句话(同源纪律)。 */ { name: 'base_price', type: 'number', primaryKey: false, masked: false, required: true, label: '底价' }, /* MOQ 常带单位与档位(1K / 2500+),按文本收,不硬转数值 */ { name: 'moq', type: 'text', primaryKey: false, masked: false, label: 'MOQ' }, { name: 'source_lead_time', type: 'text', primaryKey: false, masked: false, label: '货源交期' }, /* * **价源:取值域给出来,条数由基座现数** —— HIST(库内历史价)/ SOURCED(本单问回)。 * 价源守恒判据、分布条、价源徽读的全是基座读数;值是机器码, * 人读名(历史价/货源价)由模版样式表翻译。 */ { name: 'price_basis', type: 'text', primaryKey: false, masked: false, label: '价源', tally: ['HIST', 'SOURCED'] }, /* 备注是我方写的注,不标 expect(标了 `_gaps` 恒大于 0,门就废了 —— A1 的账) */ { name: 'note', type: 'text', primaryKey: false, masked: false, label: '备注' }, ], }, { /* 寻源结论 —— 一单一行:汇总数(哨兵与报告读)、一句人话结论 */ 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: 'quote_line_total', type: 'integer', primaryKey: false, masked: false, label: '清单条目数' }, { name: 'pending_total', type: 'integer', primaryKey: false, masked: false, label: '待补价条数' }, /* A 类预填走完的凭据(0 也要写)—— 哨兵按它放 PRM 那一步入场 */ { name: 'hist_priced_total', type: 'integer', primaryKey: false, masked: false, label: 'A 类已按历史价预填条数' }, /* 「PRM 的答复处理过了」的凭据 —— 只在人环走完才产生 */ { name: 'sourced_total', type: 'integer', primaryKey: false, masked: false, label: 'PRM 已补价条数' }, { name: 'route_decided_total', type: 'integer', primaryKey: false, masked: false, label: '已定盘' }, { name: 'summary', type: 'text', primaryKey: false, masked: false, label: '寻源结论' }, ], }, ], listeners: [ { /** 供应商回价到达:记一笔观察(PRM 在系统外收回价,这里只接得到通道转进来的) */ origin: 'static', eventType: 'data', keyFilter: { kind: 'supplier-quote' }, semantic: 'observe', deadline: 'P4D', escalation: ['nudge', 'escalate'], }, { /** 回价临过期:提醒复核确认 —— 时限必配 */ origin: 'static', eventType: 'time', keyFilter: { kind: 'quote-expiry' }, semantic: 'observe', deadline: 'P7D', escalation: ['nudge', 'reroute'], }, ], harness: { memoryWindow: 24, // PRM 在系统外询价,按场景时限最宽 BOM后期 3~9 日 —— 停滞阈值放到两天 stallAfter: 'P2D', nudge: [{ after: 'P1D', action: 'nudge' }], retry: { times: 2, backoff: 'PT5M' }, }, }, /* ── 资产初始版 ──────────────────────────────────────────────── */ assets: { businessModel: { cmmnXml: CMMN_XML, narrative: NARRATIVE, dmn: SOURCING_DMN, steps: SOURCING_STEPS, phases: SOURCING_PHASES, }, skills: SOURCING_SKILLS, /* * 两个局面各一张(与 A2 同款口径,2026-08-02): * · `sourcing-worksheet` 寻源询价工作单 —— 归一预填与 PRM 补价期间摆它: * 逐颗待询料的询价档案、按场景圈渠道的规则、价格验证的尺、发询纪律与时限, * 填回区就长在上面(素材出处 docs/MVP示例工作流.html §1.7,只作文字参考); * · `sourcing-roster` 目标报价清单 —— 汇稿定盘后与放行、归档摆它。 */ viewTemplates: [ { name: 'sourcing-worksheet', html: SOURCING_WORKSHEET }, { name: 'sourcing-roster', html: SOURCING_ROSTER }, ], cardSchemas: {}, /* * distillHooks 只留 terminal —— 上一版还有 `milestone: 'M_quotes'`, * 而案例模型里从来没有这个里程碑(死链,钩子永不触发)。 */ distillHooks: [ { on: 'terminal', prompt: '归纳本单寻源:哪些料历史价可用、货源价与历史价的偏离形态、剔单的原因分布。', }, ], }, /* ── 样例集 —— 三条形状真不同(常备案例即验收案例)───────────── */ samples: [ { /* * ① 选型转来:A2 放行载荷的真实形状 —— rows 里清单行(带 alt_pn 与现标) * 混着结论行;A 类行的取价对象是替代料。 */ name: '选型转来 · 一颗转 B 的替代料要问价(A 类行历史价预填)', input: { rfqRef: 'RFQ MD14.1', customer_code: 'CU-0142', customer_name: 'Itelma', rows: [ { rfq_ref: 'RFQ MD14.1', pn: 'BBPY00201209601Y00', customer_pn: '4-14-01502', category: '滤波器', brand: 'YAGEO', eau: 30000, alt_pn: 'PBY201209T-601Y-N', orig_type: 'C', type_tag: 'B', basis: '替代料 PBY201209T-601Y-N 库内无历史 → 转 B', }, { rfq_ref: 'RFQ MD14.1', pn: 'CJTB0101R6N', customer_pn: '4-00-04102', category: '集成电路', brand: 'Jiangsu Changjing', eau: 60000, orig_type: 'A', type_tag: 'A', }, { rfq_ref: 'RFQ MD14.1', alt_line_total: 2, c_total: 1, need_sourcing: true, route_decided_total: 1, customer_name: 'Itelma', }, ], }, expect: { ports: ['quotation'] }, }, { /* * ② 受理直达(E3 那条路):A1 载荷形状 —— records 混行,B 类两颗要问价, * A 类一颗历史价预填。 */ name: '受理直达 · 两颗 B 类要问价(records 混行形状)', input: { rfqRef: 'RFQ-2026-0555', customer_name: '青岛海洋电子', records: [ { rfq_ref: 'RFQ-2026-0555', pn: 'PESD2ETH-XR', category: '二极管', brand: 'Nexperia', eau: 30000, type_tag: 'B' }, { rfq_ref: 'RFQ-2026-0555', pn: 'PESD2ETH-XR', history_days: 9999, market_stable: false, lifecycle: '未知', type_tag: 'B', basis: '无历史/过期/波动 → B' }, { rfq_ref: 'RFQ-2026-0555', pn: 'NE555P', category: '定时器', brand: 'TI', eau: 5000, type_tag: 'B' }, { rfq_ref: 'RFQ-2026-0555', pn: 'UMW AO6604', category: '晶体管', brand: 'UMW', eau: 30000, type_tag: 'A' }, { rfq_ref: 'RFQ-2026-0555', line_total: 3, type_a_total: 1, type_b_total: 2, type_c_total: 0, need_sourcing: true }, ], }, expect: { ports: ['quotation'] }, }, { /* ③ 单飞宽进:没有单号没有清单,正文列料 —— 归一造案号,全按 B 待询 */ name: '单飞需求 · 正文列两颗料要问底价(宽进归一)', input: { subject: '两颗现货料问底价', body: '帮忙问两颗料的现货底价:SS8050 三极管 20K,XC6206P332MR LDO 10K。现货场景,只要有库存实拍的供应商。', }, expect: { ports: ['quotation'] }, }, ], entry: { file: 'index.js', checksum: 'dev-entry-quote-sourcing' }, };