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/apps/api/src/app/tools-w05.ts
T
xcsandClaude Fable 5 88f2149d12 差异比对三刀:客户约束不再一票否决 · 入参由基座从台账取 · 「下限~上限」一律读成范围
①**两颗一模一样的输入电压范围判成 MISMATCH**(用户送来 0bddd862):客户写「45V 以内」→ requirement `>= 45`,
而原 PN 与候选上限都是 40V;`judgePair` 只看候选过不过客户线、不看原 PN 自己过不过 ——
需求边界与在用料的差距记在了候选头上,硬约束 MISMATCH → 整颗不推荐。差异评估比的是「换上去会不会比现在差」:
候选与原 PN 相等 / 盖住、且客户线原 PN 也过不去 → 仍按比对结果判(MATCH,风险中),备注点名
「需求边界,请回头核对需求」;候选更窄且过不了线仍 MISMATCH(3 形有测)。
②**入参由基座从台账取,模型只报料号与客户约束**(用户:「这个做差异评估比对的 harness 肯定有问题」):
同一单痕里模型念出「输入上限 32V 低于客户 40V」—— 那是 S3 剧本示例的原话、不是这单的数;
工具入参只存哈希,喂了哪几对值事后查不到;写回时还把工具的 `equal` 改成 `less`。两头都靠模型抄、两头都没有基座核。
改三层:工具运行时把 `taskId / activityId` 交给处理器(`ToolHandler` 入参加两个可选键);
`compare-parameters` 给 `originalInternalPn + candidates[].candidateInternalPn(+ requirements)`,
参数行从 `parts.parameter`、封装 / 生命周期从 `parts.profile`、证据引用从 `parts.document` 由工具现读任务视图
(本单草稿也算),出参每项带原值与证据,写盘照抄;老形状(自带 pairs)照接,试运行走它;
container 用 `compareParametersWith(ledger)` 顶掉静态名单那份;≤800 字的工具入参整份进 `result` 痕(审得了才谈得上认真)。
**基座能读到的事实,不让模型转述给工具。**
③**「下限~上限」一律是范围**(用户:「4.5~40 / -40~+85 这种我怕模型搞不清楚」):实测解析器真有洞 ——
单位写进值里(`4.5V~40V` / `-40℃~+85℃`)被读成单个数,Unicode 减号、`…`、`≥45 / 40 以内` 读成文本。
分隔符认 ~ ~ - – — … 至 到 to、单位可夹在值里、单边界读成半开范围;出参每项 `readAs:{original,candidate}`
写明「范围 4.5~40 / 定值 5 / 下界 ≥ 45 / 文本」—— 比对依据可见,模型照它写影响、不自己重新解释值;
契约描述与 S3 / P-08 剧本明写读法(示例用中性数 1~10,不用真数据里的 4.5~40 —— 模型会把示例当这单的值)。
工具 1.0.0 → 1.3.0(注册一名一行、upsert 换版本,handler 在进程内按名绑,装完稳定版重建即生效);
W05 包 1.6.2 → 1.6.4:S3 第一拍只报料号与约束、示例只留占位形,第三拍 compare_result 照抄,S4 重比只改 requirements。
13 工具测 + 包内 89。在途单要人在审核卡上「重做」并写明意见才会重比。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0167qWjeyBwvfdGovd7FWbCu
2026-08-28 01:13:07 +08:00

100 lines
5.1 KiB
TypeScript

/**
* ACT-WORK-05 差异评估的两枚纯工具契约(活动私有作用域)。
* 两处消费:`seed-mvp.ts`(db:seed 全套播种)与 `seed-activities.ts`(activities:install 装活动时顺带注册 ——
* 「安装即用」包含它声明过的纯工具能调得动;从前 E04 的工具只在 db:seed 里注册,开发机上只跑
* activities:install 的人第一次跑活动就撞「工具不存在」)。
*/
const PURE = {
effect: 'read' as const,
errors: [] as Array<{ code: string; retryable: boolean; message: string }>,
idempotent: true,
timeoutMs: 5_000,
retries: 0,
connectionKeys: [] as string[],
pure: true,
ownerActivityId: 'part-substitution-assessment',
};
/** 逐参数比对 —— 出参键与台账列同名(difference_status / risk_level),模型照抄 */
export const COMPARE_PARAMETERS_TOOL = {
...PURE,
name: 'part-substitution-assessment.compare-parameters',
displayName: '替代料逐参数比对',
/*
* 1.1.0(2026-08-27):客户约束原 PN 自己也不满足、候选与原 PN 相等或盖住时不再判 mismatch —— 那是需求边界,不是替代差异。
* 1.2.0(同日):**比对入参由基座从台账取** —— 给 originalInternalPn + candidates[].candidateInternalPn,参数 / 封装 / 生命周期 /
* 证据引用从 parts.* 现读,模型只译客户约束(requirements);老形状(自带 pairs)照旧接。出参每项带 originalValue / candidateValue / evidenceRef。
*/
version: '1.3.0',
description:
'按硬/软约束逐参数比对原 PN 与候选 PN,输出四态 difference_status(match/mismatch/unknown/conditional)、' +
'**值的读法**:「下限~上限」一律是范围(4.5~40 = 4.5 到 40 的整段;-40~+85 = -40 到 85 的整段,不是两个数),' +
'分隔符认 ~ - … 至 到 to,单位写进值里也认;「≥45 / 45 以上」「≤40 / 40 以内」是单边界;只有一个数是定值。出参每项 readAs 摆出两边各读成了什么。' +
'compare_result、risk_level 与各候选的计数。**推荐用法**:只给 originalInternalPn 与 candidates[].candidateInternalPn,' +
'参数行从 parts.parameter、封装与生命周期从 parts.profile、证据引用从 parts.document 由工具现读(本单草稿层也算),模型不抄值;' +
'candidates[].requirements 是客户给了边界的参数 {参数名: ">= 40" / "-40~85"}。也接老形状(candidates[].pairs 自带值)。' +
'硬约束没有证据引用不判 match。候选不比原 PN 差(相等 / 盖住)而客户约束原 PN 自己也不满足的,不算替代差异:仍按比对结果判、备注点名需求边界。规则版本 R-2026.07。',
input: {
type: 'object',
properties: {
originalInternalPn: { type: 'string', description: '原 PN(内部料号);按料号从台账取参数时必填' },
candidates: {
type: 'array',
description:
'[{candidateInternalPn, requirements?: {参数名: 约束}, evidenceRef?}](从台账取值) 或 ' +
'[{candidateInternalPn, pairs:[{parameterName, originalValue, candidateValue, unit?, constraintType:"hard"|"soft", requirement?, evidenceRef?}]}](自带值)',
},
},
},
output: {
type: 'object',
properties: {
ruleVersion: { type: 'string' },
results: {
type: 'array',
description: '[{candidateInternalPn, source:"ledger"|"pairs", items:[{parameterName, originalValue, candidateValue, readAs:{original,candidate}, unit, constraintType, requirement, evidenceRef, compareResult, differenceStatus, riskLevel, note}], counts:{match,mismatch,unknown,conditional,hardMismatch,softMismatch,configurable}, extraCandidateParams?}]',
},
},
},
};
/** 推荐定级 —— 出参 recommendationLevel / riskLevel / reason 直接落 P-11 */
export const RECOMMEND_TOOL = {
...PURE,
name: 'part-substitution-assessment.recommend',
displayName: '替代推荐定级',
version: '1.0.0',
description:
'按每个候选的比对计数定推荐等级:硬约束 mismatch>0 → not_recommended;unknown>0 → conditional;' +
'只有可调配成(configurable)→ alternate;其余 conditional → conditional;全 match → recommended。',
input: {
type: 'object',
properties: {
candidates: { type: 'array', description: '[{candidateInternalPn, counts:{match,mismatch,unknown,conditional,hardMismatch,softMismatch,configurable}}]' },
},
},
output: {
type: 'object',
properties: {
ruleVersion: { type: 'string' },
results: { type: 'array', description: '[{candidateInternalPn, recommendationLevel, riskLevel, reason}]' },
},
},
};
export const W05_TOOLS = [COMPARE_PARAMETERS_TOOL, RECOMMEND_TOOL] as const;
/** 试运行审的样例(两件共用一份:并集覆盖各家入参) */
export const W05_TOOL_SAMPLE = {
input: {
candidates: [
{
candidateInternalPn: 'XL2596S-5.0',
pairs: [{ parameterName: '输出电压', originalValue: '5.0', candidateValue: '5.0', unit: 'V', constraintType: 'hard', evidenceRef: 'P-08' }],
counts: { match: 1, mismatch: 0, unknown: 0, conditional: 0, hardMismatch: 0, softMismatch: 0, configurable: 0 },
},
],
},
expectShape: { type: 'object' },
};