Archived
判据 ⑥ 跑通:A4 成本定价全链 —— 补齐取价基准这一环
A4 卡在 S3 底价自检要 `deviation`,而 S1「取价基准」调的是历史价工具,
那个工具**根本不回价格**:声明说出 deviation,工具给不出,于是永远缺一环。
按业务本意补齐:
· `crm.history-price-read` 增回 `lastPrice`(上次成交单价)—— 一个不回价的
「历史价工具」取不出价格基准;无历史的新料号回 0,由下游按「无基准」处置,
不凭空编一个偏离率。
· 新增纯工具 `quote-pricing.basis`:基准价 ↔ 拟报价 → 偏离率。
偏离率要等 S2 定出拟报价才算得出来,所以落在 S2 之后(新步 S2B)。
· A4 声明:S1 改落 `last_price`,新增 S2B,台账加 `last_price` 列。
顺带修:样例集一份喂所有工具,`quote-pricing.basis` 的必填 `quotedPrice` 不在其中,
新工具过不了两审 → 活动包安装被拒 → 旧资产继续生效,现象是「改了没反应」。
样例改为各家必填的并集。
现在 A4 跑完整条:取价基准 → DMN 定价(利润模型 P-01)→ 算偏离率 →
DMN 底价自检(底价门禁 P-02,处置=直接放行)→ 条件跳步 → 报价草案验收放行。
gates: tsc / eslint / depcruise / 397 测试 全绿
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"messageId": "rfq-20260727T@x.com",
|
||||||
|
"from": "buyer@x.com",
|
||||||
|
"to": [
|
||||||
|
"rfq@we.com"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"subject": "【询价】客户 C060 RFQ-20260727T 求报价",
|
||||||
|
"body": "客户编码 C060,询价单号 RFQ-��\nPN-AAA-111 TI 年用量 9000 目标价 6.00 交期 10w",
|
||||||
|
"receivedAt": "2026-07-27T19:00:00.000Z",
|
||||||
|
"attachments": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"messageId": "rfq-20260727U@x.com",
|
||||||
|
"from": "buyer@x.com",
|
||||||
|
"to": [
|
||||||
|
"rfq@we.com"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"subject": "【询价】客户 C070 RFQ-20260727U 求报价",
|
||||||
|
"body": "客户编码 C070,询价单号 RFQ-��\nPN-BBB-222 TI 年用量 9000 目标价 6.00 交期 10w",
|
||||||
|
"receivedAt": "2026-07-27T19:00:00.000Z",
|
||||||
|
"attachments": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"messageId": "rfq-20260727V@x.com",
|
||||||
|
"from": "buyer@x.com",
|
||||||
|
"to": [
|
||||||
|
"rfq@we.com"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"subject": "【询价】客户 C080 RFQ-20260727V 求报价",
|
||||||
|
"body": "客户编码 C080,询价单号 RFQ-��\nPN-CCC-333 TI 年用量 9000 目标价 6.00 交期 10w",
|
||||||
|
"receivedAt": "2026-07-27T19:00:00.000Z",
|
||||||
|
"attachments": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"messageId": "rfq-20260727W@x.com",
|
||||||
|
"from": "buyer@x.com",
|
||||||
|
"to": [
|
||||||
|
"rfq@we.com"
|
||||||
|
],
|
||||||
|
"cc": [],
|
||||||
|
"subject": "【询价】客户 C090 RFQ-20260727W 求报价",
|
||||||
|
"body": "客户编码 C090,询价单号 RFQ-��\nPN-DDD-444 TI 年用量 9000 目标价 6.00 交期 10w",
|
||||||
|
"receivedAt": "2026-07-27T19:00:00.000Z",
|
||||||
|
"attachments": []
|
||||||
|
}
|
||||||
@@ -59,7 +59,7 @@ export const manifest: ActivityManifest = {
|
|||||||
identity: {
|
identity: {
|
||||||
activityId: ACTIVITY_ID,
|
activityId: ACTIVITY_ID,
|
||||||
name: 'A4 · 成本定价',
|
name: 'A4 · 成本定价',
|
||||||
packageVersion: '0.1.1',
|
packageVersion: '0.1.2',
|
||||||
category: '统一报价',
|
category: '统一报价',
|
||||||
paradigm: 'flow',
|
paradigm: 'flow',
|
||||||
sdkVersion: '0.1.0-m5',
|
sdkVersion: '0.1.0-m5',
|
||||||
@@ -113,7 +113,7 @@ export const manifest: ActivityManifest = {
|
|||||||
humanCallBudget: 2,
|
humanCallBudget: 2,
|
||||||
},
|
},
|
||||||
resources: {
|
resources: {
|
||||||
tools: [{ name: 'crm.history-price-read' }],
|
tools: [{ name: 'quote-pricing.basis' }, { name: 'crm.history-price-read' }],
|
||||||
skills: [],
|
skills: [],
|
||||||
models: ['quote-extractor'],
|
models: ['quote-extractor'],
|
||||||
roles: ['BD'],
|
roles: ['BD'],
|
||||||
@@ -123,7 +123,7 @@ export const manifest: ActivityManifest = {
|
|||||||
|
|
||||||
declarations: {
|
declarations: {
|
||||||
models: ['quote-extractor'],
|
models: ['quote-extractor'],
|
||||||
tools: [{ name: 'crm.history-price-read' }],
|
tools: [{ name: 'crm.history-price-read' }, { name: 'quote-pricing.basis' }],
|
||||||
ledger: [
|
ledger: [
|
||||||
{
|
{
|
||||||
domain: DOMAIN,
|
domain: DOMAIN,
|
||||||
@@ -133,6 +133,7 @@ export const manifest: ActivityManifest = {
|
|||||||
{ name: 'pn', type: 'text', primaryKey: true, masked: false, label: 'PN' },
|
{ name: 'pn', type: 'text', primaryKey: true, masked: false, label: 'PN' },
|
||||||
{ name: 'quoted_price', type: 'number', primaryKey: false, masked: false, label: '拟报单价' },
|
{ name: 'quoted_price', type: 'number', primaryKey: false, masked: false, label: '拟报单价' },
|
||||||
{ name: 'margin', type: 'number', primaryKey: false, masked: false, label: '毛利率' },
|
{ name: 'margin', type: 'number', primaryKey: false, masked: false, label: '毛利率' },
|
||||||
|
{ name: 'last_price', type: 'number', primaryKey: false, masked: false, label: '基准价' },
|
||||||
{ name: 'deviation', type: 'number', primaryKey: false, masked: false, label: '偏离度' },
|
{ name: 'deviation', type: 'number', primaryKey: false, masked: false, label: '偏离度' },
|
||||||
{ name: 'disposition', type: 'text', primaryKey: false, masked: false, label: '门禁处置' },
|
{ name: 'disposition', type: 'text', primaryKey: false, masked: false, label: '门禁处置' },
|
||||||
],
|
],
|
||||||
@@ -167,7 +168,8 @@ export const manifest: ActivityManifest = {
|
|||||||
label: 'S1 取价基准',
|
label: 'S1 取价基准',
|
||||||
// Type A 取历史价近期价;B/C 取供应商底价表(C 类参考替代对标表)
|
// Type A 取历史价近期价;B/C 取供应商底价表(C 类参考替代对标表)
|
||||||
input: { pn: 'pn' },
|
input: { pn: 'pn' },
|
||||||
output: { domain: DOMAIN, object: 'draft', key: { pn: 'pn' }, fields: { deviation: 'deviation' } },
|
// 取回来的是**基准价**;偏离率要等 S2 定出拟报价才算得出来(见 S2B)
|
||||||
|
output: { domain: DOMAIN, object: 'draft', key: { pn: 'pn' }, fields: { last_price: 'lastPrice' } },
|
||||||
tool: 'crm.history-price-read',
|
tool: 'crm.history-price-read',
|
||||||
failure: { retry: { times: 2, backoff: 'PT1M' }, fallback: { kind: 'human', workOrder: 'execution' } },
|
failure: { retry: { times: 2, backoff: 'PT1M' }, fallback: { kind: 'human', workOrder: 'execution' } },
|
||||||
},
|
},
|
||||||
@@ -185,6 +187,15 @@ export const manifest: ActivityManifest = {
|
|||||||
tool: 'quote-pricing.margin',
|
tool: 'quote-pricing.margin',
|
||||||
failure: { retry: { times: 1, backoff: 'PT10S' }, fallback: { kind: 'blocked' } },
|
failure: { retry: { times: 1, backoff: 'PT10S' }, fallback: { kind: 'blocked' } },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
kind: 'pure-tool',
|
||||||
|
name: 'S2B_deviation',
|
||||||
|
label: 'S2B 算偏离率(基准价 ↔ 拟报价)',
|
||||||
|
input: { lastPrice: 'last_price', quotedPrice: 'quoted_price' },
|
||||||
|
output: { domain: DOMAIN, object: 'draft', key: { pn: 'pn' }, fields: { deviation: 'deviation' } },
|
||||||
|
tool: 'quote-pricing.basis',
|
||||||
|
failure: { retry: { times: 1, backoff: 'PT10S' }, fallback: { kind: 'blocked' } },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
kind: 'pure-tool',
|
kind: 'pure-tool',
|
||||||
name: 'S3_floor_gate',
|
name: 'S3_floor_gate',
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export async function seedQuote(container: Container): Promise<void> {
|
|||||||
HISTORY_TOOL,
|
HISTORY_TOOL,
|
||||||
MAIL_TOOL,
|
MAIL_TOOL,
|
||||||
CLASSIFY_TOOL,
|
CLASSIFY_TOOL,
|
||||||
|
pureTool('quote-pricing.basis', '取价基准:报价相对历史成交价的偏离率,供底价门禁 P-02 求值', 'quote-pricing', ['quotedPrice']),
|
||||||
pureTool('quote-pricing.margin', '利润模型 P-01 的确定性求值:客户等级×物料类型×数量档 → 目标毛利区间', 'quote-pricing', ['customerGrade']),
|
pureTool('quote-pricing.margin', '利润模型 P-01 的确定性求值:客户等级×物料类型×数量档 → 目标毛利区间', 'quote-pricing', ['customerGrade']),
|
||||||
pureTool('quote-pricing.floor-gate', '底价门禁 P-02 的确定性求值:偏离底价 ±20% 即止', 'quote-pricing', ['deviation']),
|
pureTool('quote-pricing.floor-gate', '底价门禁 P-02 的确定性求值:偏离底价 ±20% 即止', 'quote-pricing', ['deviation']),
|
||||||
pureTool('quote-archive.profile', '客户画像更新:把成交行为写成标签,供 P-01 的客户等级因子取用', 'quote-archive', ['customerCode']),
|
pureTool('quote-archive.profile', '客户画像更新:把成交行为写成标签,供 P-01 的客户等级因子取用', 'quote-archive', ['customerCode']),
|
||||||
@@ -129,7 +130,8 @@ export async function seedQuote(container: Container): Promise<void> {
|
|||||||
contract,
|
contract,
|
||||||
samples: [
|
samples: [
|
||||||
{
|
{
|
||||||
input: { pn: 'LM358DR', historyDays: 30, customerGrade: '普通客户', deviation: 0, to: 'x@y.com', subject: 't', customerCode: 'C001' },
|
// 样例集一份喂所有工具:并集覆盖各家必填,少一个键就有工具过不了两审
|
||||||
|
input: { pn: 'LM358DR', historyDays: 30, customerGrade: '普通客户', deviation: 0, to: 'x@y.com', subject: 't', customerCode: 'C001', quotedPrice: 9.2, lastPrice: 8.4 },
|
||||||
expectShape: { type: 'object' },
|
expectShape: { type: 'object' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -272,6 +274,14 @@ export async function seedQuote(container: Container): Promise<void> {
|
|||||||
{ target: 'customerCode', source: { from: 'initiation', path: 'customerCode' } },
|
{ target: 'customerCode', source: { from: 'initiation', path: 'customerCode' } },
|
||||||
// records 来自 A1 的输出口(受理归一的产物),不是起单入参 —— 起单时还没有这些行
|
// records 来自 A1 的输出口(受理归一的产物),不是起单入参 —— 起单时还没有这些行
|
||||||
{ target: 'records', source: { from: 'output-port', port: 'intake', path: 'records' } },
|
{ target: 'records', source: { from: 'output-port', port: 'intake', path: 'records' } },
|
||||||
|
// 逐条办的料号:MVP 单条 RFQ,取受理归一出的那一条
|
||||||
|
{ target: 'pn', source: { from: 'output-port', port: 'intake', path: 'pn' } },
|
||||||
|
// P-01 利润模型的三个因子:物料类型就是 A1 打的标(A/B/C),真实取值;
|
||||||
|
// 客户等级与数量档在 MVP 取默认档 —— 接 CRM 后改为读 shared.customer 与按 EAU 分档,
|
||||||
|
// P-01 决策表本就有兜底行('-' = 任意),默认档不会让求值失真。
|
||||||
|
{ target: 'materialType', source: { from: 'output-port', port: 'intake', path: 'type_tag' } },
|
||||||
|
{ target: 'customerGrade', source: { from: 'constant', value: '普通客户' } },
|
||||||
|
{ target: 'volumeTier', source: { from: 'constant', value: '大批量' } },
|
||||||
],
|
],
|
||||||
reopen: false,
|
reopen: false,
|
||||||
},
|
},
|
||||||
@@ -302,6 +312,14 @@ export async function seedQuote(container: Container): Promise<void> {
|
|||||||
{ target: 'customerCode', source: { from: 'initiation', path: 'customerCode' } },
|
{ target: 'customerCode', source: { from: 'initiation', path: 'customerCode' } },
|
||||||
// records 来自 A1 的输出口(受理归一的产物),不是起单入参 —— 起单时还没有这些行
|
// records 来自 A1 的输出口(受理归一的产物),不是起单入参 —— 起单时还没有这些行
|
||||||
{ target: 'records', source: { from: 'output-port', port: 'intake', path: 'records' } },
|
{ target: 'records', source: { from: 'output-port', port: 'intake', path: 'records' } },
|
||||||
|
// 逐条办的料号:MVP 单条 RFQ,取受理归一出的那一条
|
||||||
|
{ target: 'pn', source: { from: 'output-port', port: 'intake', path: 'pn' } },
|
||||||
|
// P-01 利润模型的三个因子:物料类型就是 A1 打的标(A/B/C),真实取值;
|
||||||
|
// 客户等级与数量档在 MVP 取默认档 —— 接 CRM 后改为读 shared.customer 与按 EAU 分档,
|
||||||
|
// P-01 决策表本就有兜底行('-' = 任意),默认档不会让求值失真。
|
||||||
|
{ target: 'materialType', source: { from: 'output-port', port: 'intake', path: 'type_tag' } },
|
||||||
|
{ target: 'customerGrade', source: { from: 'constant', value: '普通客户' } },
|
||||||
|
{ target: 'volumeTier', source: { from: 'constant', value: '大批量' } },
|
||||||
],
|
],
|
||||||
reopen: true,
|
reopen: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,19 +13,36 @@ import type { ToolHandler } from '@modules/toolbox';
|
|||||||
const historyPriceRead: ToolHandler = async ({ input }) => {
|
const historyPriceRead: ToolHandler = async ({ input }) => {
|
||||||
const pn = String(input['pn'] ?? '');
|
const pn = String(input['pn'] ?? '');
|
||||||
// 录制样本:演示用的历史价情报(真 CRM 接入时换 endpoint,契约不变)
|
// 录制样本:演示用的历史价情报(真 CRM 接入时换 endpoint,契约不变)
|
||||||
const table: Record<string, { historyDays: number; marketStable: boolean; lifecycle: string }> = {
|
// 历史价情报含**上次成交单价** —— 没有价的「历史价工具」是取不出价格基准的
|
||||||
LM358DR: { historyDays: 30, marketStable: true, lifecycle: '在产' },
|
const table: Record<string, { historyDays: number; marketStable: boolean; lifecycle: string; lastPrice: number }> = {
|
||||||
NE555P: { historyDays: 240, marketStable: false, lifecycle: '在产' },
|
LM358DR: { historyDays: 30, marketStable: true, lifecycle: '在产', lastPrice: 8.4 },
|
||||||
TL072CDR: { historyDays: 15, marketStable: true, lifecycle: 'EOL' },
|
NE555P: { historyDays: 240, marketStable: false, lifecycle: '在产', lastPrice: 3.25 },
|
||||||
|
TL072CDR: { historyDays: 15, marketStable: true, lifecycle: 'EOL', lastPrice: 21.6 },
|
||||||
};
|
};
|
||||||
const hit = table[pn];
|
const hit = table[pn];
|
||||||
if (hit === undefined) {
|
if (hit === undefined) {
|
||||||
// 无历史 → 走 B(重估后报);这不是错误
|
// 无历史 → 走 B(重估后报);这不是错误
|
||||||
return { ok: true, output: { historyDays: 9999, marketStable: false, lifecycle: '未知', hit: false } };
|
// 无历史 → 走 B(重估后报);没有价格基准,基准价回 0 由下游按「无基准」处置
|
||||||
|
return { ok: true, output: { historyDays: 9999, marketStable: false, lifecycle: '未知', hit: false, lastPrice: 0 } };
|
||||||
}
|
}
|
||||||
return { ok: true, output: { ...hit, hit: true } };
|
return { ok: true, output: { ...hit, hit: true } };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取价基准 —— **纯工具**:报价相对基准价的偏离率。
|
||||||
|
* 「能算的不问 LLM」:同输入必同结论。无基准价(新料号)时偏离率记 0,
|
||||||
|
* 由底价门禁 P-02 按「无基准」放行 —— 不能凭空编一个偏离率出来。
|
||||||
|
*/
|
||||||
|
const basis: ToolHandler = async ({ input }) => {
|
||||||
|
const lastPrice = Number(input['lastPrice'] ?? 0);
|
||||||
|
const quotedPrice = Number(input['quotedPrice'] ?? 0);
|
||||||
|
if (!Number.isFinite(lastPrice) || lastPrice <= 0) {
|
||||||
|
return { ok: true, output: { deviation: 0, basis: 'none', lastPrice: 0 } };
|
||||||
|
}
|
||||||
|
const deviation = (quotedPrice - lastPrice) / lastPrice;
|
||||||
|
return { ok: true, output: { deviation: Math.round(deviation * 10_000) / 10_000, basis: 'history', lastPrice } };
|
||||||
|
};
|
||||||
|
|
||||||
/** 三分类规则 P-03 的决策表 —— 与活动包里的资产同一份规则 */
|
/** 三分类规则 P-03 的决策表 —— 与活动包里的资产同一份规则 */
|
||||||
const P03_RULES = [
|
const P03_RULES = [
|
||||||
{ id: 'p03-c', when: ['-', '-', '"EOL","缺货","要求替代"'], then: { type: 'C', play: '转案处理' } },
|
{ id: 'p03-c', when: ['-', '-', '"EOL","缺货","要求替代"'], then: { type: 'C', play: '转案处理' } },
|
||||||
@@ -109,6 +126,7 @@ const mailSend: ToolHandler = async ({ input, connection }) => {
|
|||||||
export const FIRST_PARTY_TOOLS: ReadonlyArray<readonly [string, ToolHandler]> = [
|
export const FIRST_PARTY_TOOLS: ReadonlyArray<readonly [string, ToolHandler]> = [
|
||||||
['crm.history-price-read', historyPriceRead],
|
['crm.history-price-read', historyPriceRead],
|
||||||
['quote-intake.classify', classify],
|
['quote-intake.classify', classify],
|
||||||
|
['quote-pricing.basis', basis],
|
||||||
['quote-pricing.margin', margin],
|
['quote-pricing.margin', margin],
|
||||||
['quote-pricing.floor-gate', floorGate],
|
['quote-pricing.floor-gate', floorGate],
|
||||||
['quote-archive.profile', profile],
|
['quote-archive.profile', profile],
|
||||||
|
|||||||
Reference in New Issue
Block a user