活动:A2 选型 / A3 寻源 / A4 定价 / A6 议价 / A7 归档五件各补成真活动包 —— 契约宽进(input.required 空,归一在活动内)、台账必填声明(含行级 requiredWhen)、 入场哨兵与 L1/L2 判据一律读基座数、两份件(在办可编辑 + 办结静态快照)、 三案例常备、包内自测。e2e 各三案例 released、行动被拒 0、发口正确、提交闸每单真拦。 基座(共享面,改一处影响所有活动,故每处都带回归): · 重发判断走在站上场之前 —— 人答完那步常已收掉,readyToWork 先抛「不能再往它上面挂活」, 于是「重发已答过的卡 = 无事发生」这条唯一活路在最需要时够不着,A1 基线当场熔断; · 决策卡补幂等兜底(与执行工单同一把尺):头两次无事发生,第三次起才算被拒; · 输出没成形先就地重问一次,原话进工作日志、不再摆上卡面; · 工具调用读数 tool_<名>_calls —— 「机检只读基座数」这条纪律第一次覆盖到工具; · 执行卡 steps/standard 补进出参 schema(从前能到卡面全靠厂商 JSON 模式宽松); · 件的就地更新改逐节点 morph,保存先把新值镜像进只读那份 —— 不闪、不等回声。 A1(基线,全程守住):措辞三档对齐(必填/建议/选填)、禁止拿历史价当客户目标价、 卡骨架从步骤剧本搬进说明书(压成一拍连做时剧本没摊开,会发出没有指导书的裸卡)。 契约两处**新增可选字段**(非破坏):FieldDeclaration.requiredWhen、工单 finalHtml; api-report 同批更新。docs/adr 与 docs/archive 的 13 份文件在工作树里不见了, 本次予以恢复而非提交删除。 门禁:typecheck / lint / depcruise / test(1366)全绿。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
402 lines
19 KiB
TypeScript
402 lines
19 KiB
TypeScript
/**
|
|
* 活动包清单 —— 统一报价 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' },
|
|
};
|