Archived
活动: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>
582 lines
28 KiB
TypeScript
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' },
|
|
};
|