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-negotiate/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

582 lines
28 KiB
TypeScript

/**
* 活动包清单 —— 统一报价 A6 · 报价议价(案例活动)。
*
* 业务素材:`docs/MVP示例工作流.html` §1.4(S1 发报留档 → S2 跟进议价 → S3 定案交接):
* 「审批通过后 15 分钟内发出……没有跟进的报价等于没报」
* 「客户不回不是结论,催到表态或有效期过」
* 「让价在授权范围内回旋,要破底价门禁红线的交回发起流程重批,不私自破线」。
*
* 形状与 A4 同一条(2026-08-03 重写 @3.0.0):归一排版 → 一张执行单交人(发出去 +
* 把客户回音落回来)→ 逐条定案 → **还价才叫人**拍板 → 放行。
* **能算的不问人**(N-01 议价结果、N-02 让步梯度两把尺写死),
* **该客户说的不代说**(客户表态那三列 AI 一格都不写)。
*
* 与 @2.1.0 的差别(四处都是踩过的账):
* ① `input.required` 从 `['rfqRef','customerCode','items']` 清空 —— 宽进严出;
* ② 台账补 `required` / `requiredWhen` —— 从前一个必填都没有,执行卡的闸没牙;
* ③ 判据与哨兵全部换成**基座读数**(`_rows` / `_missing` / `_<列>_<值>`)——
* 从前三条 L1/L2 读 `missing_field_total` / `validity_days` / `followup_total`,
* 三个都是活动自报的数(「机检只读基座数」违反史第六类);
* ④ 三个出口补 `when` —— 去向是**算出来的**,不是模型手选的。
*/
import type { ActivityManifest, DmnModel } from '@contracts';
import { activityIdSchema } from '@contracts';
import { NEGOTIATE_SKILLS } from './skills.ts';
import { CMMN_XML, NARRATIVE, NEGOTIATE_PHASES, NEGOTIATE_STEPS } from './case-model.ts';
import { NEGOTIATE_ROSTER, NEGOTIATE_WORKSHEET } from './views/report.ts';
const D = 'quote_negotiate';
const ACTIVITY_ID = activityIdSchema.parse('quote-negotiate');
/**
* 两把尺 —— **规则本身不是数据**,随包冻结。
*
* · 议价结果 N-01:客户表态 → 定案(出参就是台账 `deal_status` 的取值);
* · 让步梯度 N-02:还价幅度 → 让步处置(出参就是台账 `concession` 的取值)。
*
* 出参键名与台账列同名,模型算完照抄即可(规范 §5.6)。
* 两把尺的档位在三处出现:这张表、剧本 guide、工作单上的规则块 ——
* 包内自测把三处钉在一起(改一处不改另两处,测就红)。
*/
export const N01_DMN: DmnModel = {
drd: [
{ decision: 'n01-outcome', requires: [], inputs: ['reply_type'] },
{ decision: 'n02-concession', requires: ['n01-outcome'], inputs: ['gap'] },
],
tables: [
{
id: 'n01-outcome',
label: '议价结果 N-01',
hitPolicy: 'FIRST',
inputs: [{ name: 'reply_type', expression: 'reply_type' }],
outputs: [{ name: 'deal_status' }],
rules: [
{ id: 'n01-1', when: ['"ACCEPT"'], then: ['WON'], annotation: '客户明确接受这个价 → 成交,转订单履约' },
{ id: 'n01-2', when: ['"COUNTER"'], then: ['HAGGLE'], annotation: '客户还了一个价 → 压在还价上,要人拍板' },
{ id: 'n01-3', when: ['"REJECT"'], then: ['LOST'], annotation: '客户明确不做了 → 不成交,情报归档' },
{ id: 'n01-4', when: ['"NOREPLY"'], then: ['LOST'], annotation: '催到有效期过仍不表态 → 按超时定案' },
{ id: 'n01-default', when: ['-'], then: ['LOST'], annotation: '认不出的表态按不成交处置 —— 不许猜成接受' },
],
},
{
/** 让步梯度 N-02 —— FIRST 命中即定,所以第二行等价于「三到十个点」 */
id: 'n02-concession',
label: '让步梯度 N-02',
hitPolicy: 'FIRST',
inputs: [{ name: 'gap', expression: 'gap' }],
outputs: [{ name: 'concession' }, { name: 'in_authority' }],
rules: [
{ id: 'n02-1', when: ['<= 0.03'], then: ['ACCEPT_COUNTER', true], annotation: '三个点以内 —— 意思意思的压价,接了拿单' },
{ id: 'n02-2', when: ['<= 0.10'], then: ['NEGOTIATE', true], annotation: '三到十个点 —— 授权内回旋一轮,让一半再报回去' },
{ id: 'n02-3', when: ['> 0.10'], then: ['ESCALATE', false], annotation: '超过一成 —— 超出授权,退回成本定价重批,不私自破线' },
],
},
],
};
export const manifest: ActivityManifest = {
identity: {
activityId: ACTIVITY_ID,
name: 'A6 · 报价议价',
packageVersion: '3.5.0',
category: '统一报价',
sdkVersion: '0.1.0-m5',
checksum: 'dev-checksum-quote-negotiate',
},
/* ── 契约七件 ────────────────────────────────────────────────── */
contract: {
activityId: ACTIVITY_ID,
version: '1.0.0',
input: {
/*
* **宽进到底:一个必填都不设**(与 A1/A2/A3/A4 同口径 —— 宽进严出,
* 输入契约在活动内部归一 + 补全)。
*
* 来路两种形状:流内转来(A4 `draft` 口的 `{rfqRef, customerCode, items}`)、
* 单飞(一段文字)。上一版必填 `rfqRef + customerCode + items`,门口拦的恰是主路。
*/
data: {
type: 'object',
required: [],
properties: {
rfqRef: { type: 'string', title: '询价单号(单标识,贯穿全流程)' },
customerCode: { type: 'string', title: '客户码' },
items: { type: 'array', title: '报价草案条目(已批准,每条带拟报单价与门禁处置)' },
rows: { 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: '读出来的文本' },
},
},
},
},
},
messageAllowed: true,
messageHint: '客户的议价习惯、竞对价位、这一单的让价授权、催办节奏写这里',
},
output: {
/*
* **没有必交付口**:这个活动的成品就是「谈出一个结果」,而结果本身是分支 ——
* 三口恰发一口,发出去的那一口就是这一单的 outcome(基座在放行时把口名
* 写进事实,下游 E11/E12/E13 读的就是它)。
*/
mandatory: [],
/*
* **三条出边 = 一个 XOR 选择组**(对齐流程包 E11/E12/E13)。
* 每一口都写了 `when`(读基座按值现数的 tally 读数),这一单往哪走是**算出来的**:
* 放行时基座按序求值首个命中即出,卡上只如实告诉人走哪一口、凭哪一条。
*
* 顺序即优先级:**判了退回重批 → 先退回重批**(谈不拢的那几条不能被
* 「另外几条成交了」盖过去);其次有成交 → 转履约;都没成 → 转归档。
*/
selective: [
{
group: 'negotiation',
ports: [
{
name: 'revise',
payload: {
type: 'object',
required: ['rfqRef', 'outcome'],
properties: {
rfqRef: { type: 'string', title: '询价单号' },
customerCode: { type: 'string', title: '客户码' },
outcome: { type: 'string', title: '议价结果(固定 revise)' },
reason: { type: 'string', title: '退回缘由(让幅超授权多少)' },
rows: { type: 'array', title: '报价函(带客户还价与让步处置)' },
},
},
description: '有条目经人拍板判了退回重批 —— 让幅超出授权,回成本定价重算再谈',
when: `${D}_line_deal_status_revise > 0`,
},
{
name: 'won',
payload: {
type: 'object',
required: ['rfqRef', 'outcome'],
properties: {
rfqRef: { type: 'string', title: '询价单号' },
customerCode: { type: 'string', title: '客户码' },
quoteNo: { type: 'string', title: '报价单号' },
outcome: { type: 'string', title: '议价结果(固定 won)' },
rows: { type: 'array', title: '成交条目(带成交价)' },
},
},
description: '有条目谈成 —— 转订单履约(ORM)',
when: `${D}_line_deal_status_won > 0`,
},
{
/* 下游 A7 的入参要 `rfqRef / customerCode / quoteNo` —— 边读什么、下游要什么,这一口就得发什么 */
name: 'lost',
payload: {
type: 'object',
required: ['rfqRef', 'customerCode', 'quoteNo', 'outcome'],
properties: {
rfqRef: { type: 'string', title: '询价单号' },
customerCode: { type: 'string', title: '客户码' },
quoteNo: { type: 'string', title: '报价单号(A7 归档的主键之一)' },
outcome: { type: 'string', title: '议价结果(固定 lost)' },
reason: { type: 'string', title: '不成交缘由(拒绝 / 超时无回复)' },
rows: { type: 'array', title: '报价函(带客户表态与定案)' },
},
},
description: '一条都没谈成:客户拒绝或超时无回复 —— 转情报归档',
when: `${D}_line_deal_status_lost > 0`,
},
],
},
],
},
/*
* 必交付物只有一件:**正式报价函**。议价结论(verdict)是它的结论栏,
* `ledgers` 说清这份成果由哪几张表承载(一份成果,不是两份)。
*/
requiredDeliverables: [
{
key: 'formal-quote',
label: '正式报价函(逐条条款 + 客户表态与定案)',
ledger: `${D}.line`,
/* 验收页上让 BD 就地改的两列 —— 恰是他的职权面(成交价与客户原话) */
editableAtRelease: ['final_price', 'reply_note'],
ledgers: [`${D}.line`, `${D}.verdict`],
},
],
/*
* 验收判据 —— **机检(L1/L2)只读基座数得出来的数**:
* `_rows` 基座数行数、`_missing` 基座按 required / requiredWhen 现数
* (与执行卡提交闸同一个数同一套算法)、`_<列>_<值>` 基座按值现数。
* 上一版三条机检读活动自报的 `missing_field_total` / `validity_days` /
* `followup_total` —— 那不是门,是「改哪个数能过」的提示。
*/
acceptanceCriteria: [
{
key: 'roster-complete',
label: '报价函齐:上游条目逐条承继,说了几条报价函里就躺着几行',
level: 'L1',
predicate: `line_total > 0 and ${D}_line_rows = line_total`,
},
{
key: 'replied-all',
label: '每条都有客户表态:还价的那几条还落下了客户报的那个价,缺一行放不了',
level: 'L1',
predicate: `${D}_line_rows > 0 and ${D}_line_missing = 0`,
},
{
key: 'reply-typed',
label: '表态逐条落成四种之一:接受 / 还价 / 拒绝 / 无回复 —— 没有含糊其辞的格子',
level: 'L2',
predicate: `${D}_line_reply_type_accept + ${D}_line_reply_type_counter + ${D}_line_reply_type_reject + ${D}_line_reply_type_noreply = ${D}_line_rows`,
},
{
key: 'settled',
label: '定案逐条落定:成交 / 待拍板 / 退回重批 / 不成交,四档相加等于报价函条数',
level: 'L2',
modelRef: 'n01-outcome',
predicate: `${D}_line_rows > 0 and ${D}_line_deal_status_won + ${D}_line_deal_status_haggle + ${D}_line_deal_status_revise + ${D}_line_deal_status_lost = ${D}_line_rows`,
},
{
/* 与 A4 的「拦截档清零」同一形状:还价的条目要么接、要么退回重批、要么守价不让 */
key: 'haggle-cleared',
label: '客户还的价都拍过板了:接了、退回重批了、还是守价不让 —— 没有还悬着的条目',
level: 'L2',
predicate: `${D}_line_deal_status_haggle = 0`,
},
{
/*
* 退回重批的行**必是客户还过价的行** —— 两边都是基座按值现数。
* 没人还价却判了「退回重批」,是模型给自己找了一条不必叫人的近路(实测栽过一轮)。
*/
key: 'revise-from-counter',
label: '判了退回重批的,都是客户真还过价的那几条 —— 没有凭空退回的条目',
level: 'L2',
predicate: `${D}_line_deal_status_revise <= ${D}_line_reply_type_counter`,
},
/*
* 议价过程可核是**裁量**(L3):客户的话记没记全、让价的理由站不站得住,
* 机器数不出来。这句 label 与剧本里 judge 的 criterion **必须逐字一致**。
*/
{
key: 'negotiation-defensible',
label: '议价过程说得清:报价函条款齐备、每一轮客户反馈与让价理由逐条可溯,BD 对这个结果负责',
level: 'L3',
},
/* L5 是责任关:那张卡本身就是这道关,卡面上不摆它(基座统一滤) */
{ key: 'bd-release', label: 'BD 放行:签下去这一单的议价结果即定案并按去向交接', level: 'L5' },
],
revisableAtRelease: [],
prohibitions: [
{ key: 'no-speak-for-customer', statement: '不许替客户表态:客户没回的行按无回复落,不得写成接受 —— 那是在系统里伪造一份客户承诺' },
{ key: 'no-break-line', statement: '客户要的价超出让价授权的,退回成本定价重批,不私自破线' },
{ key: 'no-send-before-release', statement: '报价函在放行前不得对外发出 —— 触达世界的动作集中在放行那一刻' },
],
authorization: {
discretion: [{ key: 'haggle-band', statement: '让步梯度 N-02 给的授权区间内自决;超过一成的让幅一律回成本定价重批' }],
preauthorizedActions: [],
/* 一张执行单 + 可能一张决策卡 + 放行 = 三次;留一倍余量 */
humanCallBudget: 5,
},
resources: {
/*
* **不调工具**。上一版声明了 `mail.send`(写类:登记意向、放行时落地)——
* 而「把报价函发给客户」这件事在 MVP 示例工作流 §1.4 里本来就是**人工任务**:
* 收信的邮箱是 BD 的,客户回什么也只有他看得见。让活动登记一条发信意向,
* 既要过落地预检(收件人 / 主题 / 工具授权三道),又与执行单上那句
* 「请把它发给客户」重复,而没人说得清究竟哪一份才真发了出去。
*/
tools: [],
skills: NEGOTIATE_SKILLS.map((s) => s.id),
models: ['deepseek-v4-flash'],
roles: ['BD'],
/* SLA:15 分钟内发出、D+3/D+7 催、有效期 7 天;预算按 A4 实测(6-9 拍)留一倍余量 */
budget: { steps: 60, tokens: 400_000, activeMinutes: 120, humanCalls: 5 },
},
},
/* ── 声明 ────────────────────────────────────────────────────── */
declarations: {
models: ['deepseek-v4-flash'],
tools: [],
ledger: [
{
domain: D,
object: 'line',
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: 'lead_time', type: 'text', primaryKey: false, masked: false, label: '交期' },
/* 客户备注承继自上游 —— 认证要求、预授权、急料标注,议价时的第一手依据 */
{ name: 'remark', type: 'text', primaryKey: false, masked: false, label: '客户备注(约束/急料)' },
{ name: 'type_tag', type: 'text', primaryKey: false, masked: false, label: '类别(承继)', tally: ['A', 'B', 'C'] },
/* ── 报价函的条款面:发出去就是对客户的承诺 ─────────────────── */
{ name: 'quoted_price', type: 'number', primaryKey: false, masked: false, label: '报价 CNY(已批准)' },
{ name: 'spq', type: 'integer', primaryKey: false, masked: false, label: 'SPQ 最小包装' },
{ name: 'moq', type: 'integer', primaryKey: false, masked: false, label: 'MOQ 最小起订' },
{ name: 'trade_terms', type: 'text', primaryKey: false, masked: false, label: '贸易条款' },
{ name: 'valid_until', type: 'date', primaryKey: false, masked: false, label: '有效期至' },
/*
* ── 客户那一侧的三列:**AI 一格都不写** ────────────────────────
*
* 与 A4 的 `quoted_price` 同一处结构治:一条要模型分辨「这行能不能替客户填」
* 的规矩会走岔;「这几列不写」没有分支可走岔。
*
* `reply_type` 标 `required` —— 件上的缺项红带、执行卡的提交闸、放行门 L1
* 三处同源(都数基座的 `_missing`)。
* `counter_price` 用 `requiredWhen`:**只有还价那一行才算必填**
* (表级 required 会让接受/拒绝的行永远缺一格,把闸拦成死锁)。
*/
{
name: 'reply_type',
type: 'text',
primaryKey: false,
masked: false,
required: true,
label: '客户表态',
tally: ['ACCEPT', 'COUNTER', 'REJECT', 'NOREPLY'],
},
{
name: 'counter_price',
type: 'number',
primaryKey: false,
masked: false,
requiredWhen: { column: 'reply_type', value: 'COUNTER' },
label: '客户还价',
},
{ name: 'reply_note', type: 'text', primaryKey: false, masked: false, label: '客户原话 / 让价理由' },
/* ── 定案面:两把尺算出来的,模型照抄 ─────────────────────────── */
/*
* 定案四档,**HAGGLE 是过渡态**(客户还了价、还没拍板):拍板那一下把它换成
* WON / REVISE / LOST 之一,于是「还价才叫人」那道哨兵当场为假。
* 上一版只有三档、退回重批那条路「定案不动」—— 人点完了 haggle 还是那个数,
* 哨兵照旧成立,模型读同一段做法就再发一张卡:**实测同一张连发六次**。
* 措辞治不了这种事,得让「办完了」在数上看得见(2026-08-03 逼出来的账)。
*/
{ name: 'deal_status', type: 'text', primaryKey: false, masked: false, label: '定案', tally: ['WON', 'HAGGLE', 'REVISE', 'LOST'] },
{
name: 'concession',
type: 'text',
primaryKey: false,
masked: false,
label: '让步处置(N-02)',
tally: ['ACCEPT_COUNTER', 'NEGOTIATE', 'ESCALATE'],
},
{ name: 'final_price', type: 'number', primaryKey: false, masked: false, label: '成交价' },
],
},
{
/*
* 议价结论 —— 一单一行的**汇总表**,哨兵与报告读它。
* 汇总列与逐行列**故意不同名**:任务视图把草稿行摊平到顶层是「后写的赢」,
* 拿逐行列求值只会验到最后写的那一行,那不叫机检、叫抽样(A1 的教训)。
*/
domain: D,
object: 'verdict',
kind: 'record',
caseKey: 'rfq_ref',
label: '议价结论',
fields: [
{ name: 'rfq_ref', type: 'text', primaryKey: true, masked: false, label: '询价单号' },
{ name: 'quote_no', type: 'text', primaryKey: false, masked: false, label: '报价单号' },
{ name: 'customer_code', type: 'text', primaryKey: false, masked: false, label: '客户码' },
{ name: 'customer_name', type: 'text', primaryKey: false, masked: false, label: '客户名称' },
{ name: 'line_total', type: 'integer', primaryKey: false, masked: false, label: '报价函条数' },
{ name: 'valid_days', type: 'integer', primaryKey: false, masked: false, label: '报价有效期天数' },
/* 排版走完的凭据(0 也要写)—— 哨兵按它放交人发出那一步入场 */
{ name: 'composed_total', type: 'integer', primaryKey: false, masked: false, label: '已排版条数' },
/* 「客户回音落齐了」的凭据 —— 只在人环走完才产生 */
{ name: 'replied_total', type: 'integer', primaryKey: false, masked: false, label: '已落客户表态条数' },
{ name: 'settled_total', type: 'integer', primaryKey: false, masked: false, label: '已定案条数' },
{ name: 'route_decided_total', type: 'integer', primaryKey: false, masked: false, label: '已定案定盘' },
{ name: 'outcome', type: 'text', primaryKey: false, masked: false, label: '议价结果(won/revise/lost)' },
{ name: 'reason', type: 'text', primaryKey: false, masked: false, label: '不成交 / 退回缘由' },
{ name: 'summary', type: 'text', primaryKey: false, masked: false, label: '议价结论' },
],
},
],
/*
* 三条静态监听 —— **时限必配**(AM 4.4)。等客户回音这件事的落单人是 BD
* (见 case-model 头注),这三条管的是「别把它挂到天荒地老」:
* D+3 催一轮、D+7 最后一催并升级、客户真从通道回信时也认得出是这一单。
*/
listeners: [
{
origin: 'static',
eventType: 'time',
keyFilter: { kind: 'followup-d3' },
semantic: 'observe',
deadline: 'P3D',
escalation: ['nudge'],
},
{
origin: 'static',
eventType: 'time',
keyFilter: { kind: 'quote-expiry' },
semantic: 'observe',
deadline: 'P7D',
escalation: ['nudge', 'escalate'],
},
{
origin: 'static',
eventType: 'data',
keyFilter: { kind: 'customer-reply' },
semantic: 'observe',
deadline: 'P7D',
escalation: ['nudge'],
},
],
harness: {
memoryWindow: 22,
// 报价有效期 P-04 是 7 天,但这一单的**活办完**只需两个人环
stallAfter: 'PT2H',
nudge: [{ after: 'PT1H', action: 'nudge' }],
retry: { times: 2, backoff: 'PT30S' },
},
},
/* ── 资产初始版 ──────────────────────────────────────────────── */
assets: {
businessModel: {
cmmnXml: CMMN_XML,
narrative: NARRATIVE,
dmn: N01_DMN,
steps: NEGOTIATE_STEPS,
phases: NEGOTIATE_PHASES,
},
skills: NEGOTIATE_SKILLS,
/*
* 两个局面各一张(与 A2/A3/A4 同款口径):
* · `negotiate-worksheet` 议价工作单 —— 排版、发出与落回音期间摆它(逐颗报价档案、
* 两把尺、填回工作表);
* · `negotiate-roster` 成交定案清单 —— 拍板、放行、归档摆它。
*/
viewTemplates: [
{ name: 'negotiate-worksheet', html: NEGOTIATE_WORKSHEET },
{ name: 'negotiate-roster', html: NEGOTIATE_ROSTER },
],
cardSchemas: {},
distillHooks: [
{
on: 'terminal',
prompt: '归纳本单议价:客户的还价幅度与说法、让步梯度哪一档最常命中、成交与不成交的关键因素。',
},
],
},
/* ── 样例集 —— 三条形状真不同(常备案例即验收案例)───────────── */
samples: [
{
/*
* ① 流内常路:A4 `draft` 口的真实形状 —— items 里草案行(带批准价与门禁处置)
* 混着结论行。客户一轮就全接受,一个人都不必额外惊动 → won。
*/
name: '流内转来 · 两条已批准 · 客户一轮全接受(常路)',
input: {
rfqRef: 'RFQ MD14.1',
customerCode: 'CU-0142',
customer_name: 'Itelma',
items: [
{
rfq_ref: 'RFQ MD14.1',
pn: 'BBPY00201209601Y00',
customer_pn: '4-14-01502',
category: '滤波器',
brand: 'YAGEO',
eau: 30000,
lead_time: '8周',
type_tag: 'B',
quoted_price: 0.085,
disposition: 'PASS',
price_note: '客户长单,按目标价接',
},
{
rfq_ref: 'RFQ MD14.1',
pn: 'CJTB0101R6N',
customer_pn: '4-00-04102',
category: '集成电路',
brand: 'Jiangsu Changjing',
eau: 60000,
lead_time: '6周',
type_tag: 'A',
quoted_price: 0.42,
disposition: 'PASS',
},
{ rfq_ref: 'RFQ MD14.1', quote_line_total: 2, customer_name: 'Itelma', summary: '两条已批准,交议价' },
],
},
expect: { ports: ['won'] },
},
{
/*
* ② 客户压价:一条还了价、压得超过一成 —— 让步梯度判 ESCALATE(超出授权),
* 必须叫人拍板(决策卡);拍板选「退回成本定价重批」→ revise。
*/
name: '客户压价一成以上 · 决策卡拍板 → 退回定价重批',
input: {
rfqRef: 'RFQ-2026-0777',
customerCode: 'CU-0311',
customer_name: '青岛海洋电子',
items: [
{
rfq_ref: 'RFQ-2026-0777',
pn: 'PESD2ETH-XR',
category: '二极管',
brand: 'Nexperia',
eau: 30000,
lead_time: '8周',
type_tag: 'B',
quoted_price: 0.098,
disposition: 'NOTE',
price_note: '竞对价压,已让到底价线内',
remark: '客户明说这个价是竞对给的,要跟',
},
{
rfq_ref: 'RFQ-2026-0777',
pn: 'NE555P',
category: '定时器',
brand: 'TI',
eau: 5000,
lead_time: '4周',
type_tag: 'A',
quoted_price: 1.05,
disposition: 'PASS',
},
{ rfq_ref: 'RFQ-2026-0777', quote_line_total: 2, customer_name: '青岛海洋电子' },
],
},
expect: { ports: ['revise'] },
},
{
/*
* ③ 单飞宽进:没有单号没有清单,正文说了发给谁、报什么价 —— 归一造案号;
* 客户两轮催问都不回,按超时定案 → lost(下游 A7 情报归档)。
*/
name: '单飞需求 · 正文列两颗料 · 客户 D+7 无回复(超时定案)',
input: {
subject: 'MD14.2 报价函发客户',
body: '定价审批已通过:XCJ-7788T 电源模块 8K,批准价 14.80;LMR-3302QQ 稳压器 15K,批准价 2.35。客户是新客户 深圳市锐驰电子,联系人王工 wang@ruichi.example.cn,交期都按 6 周报。请发正式报价函并按 D+3/D+7 跟进。',
},
expect: { ports: ['lost'] },
},
],
entry: { file: 'index.js', checksum: 'dev-entry-quote-negotiate' },
};