This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-TMP-REPO/activities/quote-sourcing/src/manifest.ts
T
ChaiHaoandClaude Opus 5 ade0fa5a94 A2-A7 全部补齐到验收线;顺手把几处「门拦住却不给路」的坑填了
活动: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>
2026-08-03 12:28:31 +08:00

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' },
};