Archived
人手改的那一手真正落得下去;界面不撒谎那几处补齐
手改是**兜 AI 的错**的那一手 —— 这一轮把它从「摆在那儿但落不了地」做成真的: 件上就地改 · 保存**按地址收**,不看当下那张卡:载荷从前借道工单的 `blocks[i]` 取列名, 于是熔断卡(没有 blocks)在屏上时,人补的那一格一个字都送不出去 —— 而他要补的恰恰是把这一单卡住的那个必填格。授权面改由活动清单的台账声明把关。 · **能增删行**:错不止「这一格填错了」,还有「这一行不该在」与「漏了一行」。 删挂记号不当场抹掉(看得见、可后悔),且**删要说出来**(`drops`)—— 「没回传就是删了」会把一次不完整的回传变成一次删表。 · **必填没填按不下提交**:与放行门 L1 同一个数同一套算法;闸上摆出差在哪一行哪一格。 · 只写真改了的那几行 —— 改一格却留「修订 4 行」的痕,那句话是假的。 判据与门 · 裁量判据带谓词时**谓词说了算**:「选填缺口已补齐」基座数得出来, 人填好的目标价它照样判「客户未提供」的毛病由此消掉。 · 验收卡判据**读时现算** —— 人在成果件上补一格,判据跟着变。 · 放行门认「**办完了没有**」不认「有人写过」:从前写个 `false` 就把门推开, 于是一单没打标就进了放行门。三个数全由基座现数。 · 拒回把「活没干完」说成「数或行对不上」的毛病:判据点名的表一行都没写时直说那一步没办。 **指向两扇打不开的门,比不指还糟。** 界面不撒谎 · 日志只有三色:负责人绿 · 智能体蓝 · 系统灰(归侧五种是度量的分法,不是给人看的)。 · 验收收口答两件事:落了什么(按**交付物**点名,一份成果的几张表并成一行)、 往哪儿去(转出哪一口、带过去哪些字段);一个口都没发就直说到此为止。 · 签完到收尾之间那一段有自己的话「字已签下,正在落地」—— 中间态本来就该说出来,只是要说对。 · 消息不再被内部事件刷屏:只有从通道进来的件才谈得上「无人认领」。 · 建客户公司与联系人**一次确认落两行**;联系人编码自增不问人;卡面摆表的人读名。 A1 三案例端到端全 released:全 A 类直通 12 拍 / 客户点名跳一条 10 拍 / 新客户缺必填 14 拍。 五道门禁全绿(971 passed)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsTDjoHovERXyy6Wmw1ALp
This commit is contained in:
@@ -9,23 +9,60 @@ import type { ToolHandler } from '@modules/toolbox';
|
||||
/**
|
||||
* 查历史价 —— 外部系统(CRM)的**录制回放替身**。
|
||||
* README:mock 只允许出现在外界;系统内部机制一律真跑。
|
||||
*
|
||||
* **一次可查一批**(2026-07-30 加 `pns`):一单几十条料,一条一调就是几十拍模型 ——
|
||||
* 每拍都要重读一遍上下文,而拿回来的东西一模一样。契约演进只增不破坏:
|
||||
* `pn` 仍收单条(A4 定价那侧照旧),新增的 `pns` 是可选入参,出参多一个 `rows`。
|
||||
*/
|
||||
const historyPriceRead: ToolHandler = async ({ input }) => {
|
||||
const pn = String(input['pn'] ?? '');
|
||||
const HISTORY_SAMPLES: Record<string, { historyDays: number; marketStable: boolean; lifecycle: string; lastPrice: number }> = {
|
||||
// 录制样本:演示用的历史价情报(真 CRM 接入时换 endpoint,契约不变)
|
||||
// 历史价情报含**上次成交单价** —— 没有价的「历史价工具」是取不出价格基准的
|
||||
const table: Record<string, { historyDays: number; marketStable: boolean; lifecycle: string; lastPrice: number }> = {
|
||||
LM358DR: { historyDays: 30, marketStable: true, lifecycle: '在产', lastPrice: 8.4 },
|
||||
NE555P: { historyDays: 240, marketStable: false, lifecycle: '在产', lastPrice: 3.25 },
|
||||
TL072CDR: { historyDays: 15, marketStable: true, lifecycle: 'EOL', lastPrice: 21.6 },
|
||||
};
|
||||
const hit = table[pn];
|
||||
if (hit === undefined) {
|
||||
// 无历史 → 走 B(重估后报);这不是错误
|
||||
// 无历史 → 走 B(重估后报);没有价格基准,基准价回 0 由下游按「无基准」处置
|
||||
return { ok: true, output: { historyDays: 9999, marketStable: false, lifecycle: '未知', hit: false, lastPrice: 0 } };
|
||||
LM358DR: { historyDays: 30, marketStable: true, lifecycle: '在产', lastPrice: 8.4 },
|
||||
NE555P: { historyDays: 240, marketStable: false, lifecycle: '在产', lastPrice: 3.25 },
|
||||
TL072CDR: { historyDays: 15, marketStable: true, lifecycle: 'EOL', lastPrice: 21.6 },
|
||||
|
||||
/*
|
||||
* ── Itelma MD14.1 的九条 —— **老客户测试案例的情报底账** ────────────────
|
||||
*
|
||||
* 数字不是编的:逐条照 `JCAI_PRD/docs/活动执行页/A1-询价受理记录.html` 那份
|
||||
* 验收目标里写明的台账价与判据抄下来,天数按来件日 07-10 折算。
|
||||
* 抄下来是为了让**三分类真判得出** 5A / 3B / 1C —— 而那一分类决定这一单
|
||||
* 走哪个出口(有 C 类 → 去 A2 选型)。情报造得随意一点,整条流就走岔了。
|
||||
*
|
||||
* 没录进来的两条(TVM6C160K203R 压敏电阻、DAG2400L321504XZB01-R0 定制天线)
|
||||
* **是故意不录的**:台账里本来就没有它们的历史价,查不到会回 historyDays=9999,
|
||||
* 命中 B 类「重估后报」—— 那正是验收目标上写的结论。
|
||||
*/
|
||||
'PESD2ETH-XR': { historyDays: 59, marketStable: true, lifecycle: '在产', lastPrice: 0.225 },
|
||||
BSCH00160808R12J00: { historyDays: 25, marketStable: true, lifecycle: '在产', lastPrice: 0.057 },
|
||||
// 过期 121 天:天数一过 90 就落 B,必须重询锁价
|
||||
BWVF005050201R0T00: { historyDays: 121, marketStable: true, lifecycle: '在产', lastPrice: 0.088 },
|
||||
CJTB0101R6N: { historyDays: 43, marketStable: true, lifecycle: '在产', lastPrice: 0.195 },
|
||||
'SGM2202-ADJYN6G/TR': { historyDays: 38, marketStable: true, lifecycle: '在产', lastPrice: 0.47 },
|
||||
'UMW AO6604': { historyDays: 29, marketStable: true, lifecycle: '在产', lastPrice: 0.27 },
|
||||
// 原厂交期 16 周顶不住首批,客户已预授权 Analog —— 生命周期记「要求替代」,P-03 判 C
|
||||
BBPY00201209601Y00: { historyDays: 47, marketStable: true, lifecycle: '要求替代', lastPrice: 0.061 },
|
||||
};
|
||||
|
||||
/** 无历史 → 走 B(重估后报);**这不是错误**,新料号本就该重估,极大天数本身就是判据 */
|
||||
const NO_HISTORY = { historyDays: 9999, marketStable: false, lifecycle: '未知', hit: false, lastPrice: 0 };
|
||||
|
||||
const lookupHistory = (pn: string) => {
|
||||
const hit = HISTORY_SAMPLES[pn];
|
||||
return hit === undefined ? { ...NO_HISTORY } : { ...hit, hit: true };
|
||||
};
|
||||
|
||||
const historyPriceRead: ToolHandler = async ({ input }) => {
|
||||
const batch = input['pns'];
|
||||
if (Array.isArray(batch)) {
|
||||
const rows = batch.map((raw) => {
|
||||
const pn = String(raw ?? '');
|
||||
return { pn, ...lookupHistory(pn) };
|
||||
});
|
||||
return { ok: true, output: { rows } };
|
||||
}
|
||||
return { ok: true, output: { ...hit, hit: true } };
|
||||
const pn = String(input['pn'] ?? '');
|
||||
return { ok: true, output: { pn, ...lookupHistory(pn) } };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -44,23 +81,79 @@ const basis: ToolHandler = async ({ input }) => {
|
||||
};
|
||||
|
||||
/** 三分类规则 P-03 的决策表 —— 与活动包里的资产同一份规则 */
|
||||
/*
|
||||
* 三分类规则 P-03 的决策表 —— 与活动包里的资产同一份规则。
|
||||
* 出参键名用 `type_tag` 与台账列同名:模型拿回结果直接 write,不必自己改名
|
||||
* (改名这一步交给模型做,它会漏)。`basis` 一并给出,分级依据不必再编。
|
||||
*/
|
||||
const P03_RULES = [
|
||||
{ id: 'p03-c', when: ['-', '-', '"EOL","缺货","要求替代"'], then: { type: 'C', play: '转案处理' } },
|
||||
{ id: 'p03-a', when: ['<= 90', 'true', '-'], then: { type: 'A', play: '直接快答' } },
|
||||
{ id: 'p03-b', when: ['-', '-', '-'], then: { type: 'B', play: '重估后报' } },
|
||||
{ id: 'p03-c', when: ['-', '-', '"EOL","缺货","要求替代"'], then: { type_tag: 'C', play: '转案处理', basis: 'EOL/缺货/要求替代 → C' } },
|
||||
{ id: 'p03-a', when: ['<= 90', 'true', '-'], then: { type_tag: 'A', play: '直接快答', basis: '历史价≤90天且行情稳 → A' } },
|
||||
{ id: 'p03-b', when: ['-', '-', '-'], then: { type_tag: 'B', play: '重估后报', basis: '无历史/过期/波动 → B' } },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* 逐条打标 —— **纯工具**:无外效应、无连接配置、活动私有作用域。
|
||||
* 「能算的不问 LLM」:同输入必同结论,零方差零 token;命中规则行随出参留痕。
|
||||
*
|
||||
* **一次可打一批**(`rows`):判据是逐条的,结论也是逐条的,但求值不必逐拍 ——
|
||||
* 一单几十条料分几十拍打标,烧掉的全是重复上下文。
|
||||
*/
|
||||
const classify: ToolHandler = async ({ input }) => {
|
||||
const values = [input['historyDays'], input['marketStable'], input['lifecycle']];
|
||||
const classifyOne = (values: readonly unknown[]) => {
|
||||
for (const rule of P03_RULES) {
|
||||
const hit = rule.when.every((cell, i) => evaluateUnaryTest(cell, values[i], {}));
|
||||
if (hit) return { ok: true, output: { ...rule.then, rule: rule.id } };
|
||||
if (rule.when.every((cell, i) => evaluateUnaryTest(cell, values[i], {}))) {
|
||||
return { ...rule.then, rule: rule.id };
|
||||
}
|
||||
}
|
||||
return { ok: false, error: { code: 'no-match', retryable: false, message: '三分类规则无命中行' } };
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** 判据三件的取值:下划线与驼峰两认 —— 台账列是下划线,模型常写驼峰 */
|
||||
const basisOf = (row: Record<string, unknown>) => [
|
||||
row['history_days'] ?? row['historyDays'],
|
||||
row['market_stable'] ?? row['marketStable'],
|
||||
row['lifecycle'],
|
||||
];
|
||||
|
||||
const classify: ToolHandler = async ({ input }) => {
|
||||
const batch = input['rows'];
|
||||
if (Array.isArray(batch)) {
|
||||
const results: Array<Record<string, unknown>> = [];
|
||||
for (const raw of batch) {
|
||||
const row = (typeof raw === 'object' && raw !== null ? raw : {}) as Record<string, unknown>;
|
||||
const decided = classifyOne(basisOf(row));
|
||||
if (decided === undefined) {
|
||||
// 一条判不出就整批不出结论:半份分类表比没有更难查
|
||||
return {
|
||||
ok: false,
|
||||
error: { code: 'no-match', retryable: false, message: `三分类规则无命中行:${String(row['pn'] ?? '(无 PN)')}` },
|
||||
};
|
||||
}
|
||||
results.push({ pn: String(row['pn'] ?? ''), ...decided });
|
||||
}
|
||||
/*
|
||||
* **条数由判定器自己数**(2026-07-31 实测逼出来的)。
|
||||
*
|
||||
* 三类条数是验收判据的入参(route-alt / route-rfq 拿它跟派单数对账)。
|
||||
* 从前只回逐条结论,条数由模型在脑子里数 —— 实测数错过:判定器给的是 5A/3B/1C,
|
||||
* 模型报的是 7A/2B/1C,而派单也按它自己那个数开(2 张询价请求)。
|
||||
* 于是等式 `rfq_request_total = type_b_total` 两边都是它自报的,**2 = 2 顺利过关**,
|
||||
* 而真实的第三条 B 类料一张单也没开 —— 靠自报两边的对账,防的是疏忽,防不了口径。
|
||||
*
|
||||
* 数数是能算的事,算完摆在出参里,模型照抄即可。
|
||||
*/
|
||||
const counts = { A: 0, B: 0, C: 0 } as Record<string, number>;
|
||||
for (const r of results) {
|
||||
const tag = String(r['type_tag'] ?? '');
|
||||
if (tag in counts) counts[tag] = (counts[tag] ?? 0) + 1;
|
||||
}
|
||||
return { ok: true, output: { results, counts, total: results.length } };
|
||||
}
|
||||
const decided = classifyOne(basisOf(input));
|
||||
if (decided === undefined) {
|
||||
return { ok: false, error: { code: 'no-match', retryable: false, message: '三分类规则无命中行' } };
|
||||
}
|
||||
return { ok: true, output: decided };
|
||||
};
|
||||
|
||||
/** 利润模型 P-01:品牌 × 类型 × 客户等级 × 数量档 → 目标毛利区间 */
|
||||
|
||||
Reference in New Issue
Block a user