Archived
feat(w05): 五条样例与 e2e 套件就位,包已装库并经 SQL 核对 —— 五案例尚未跑
Task 11 的 Step 1-3。装包结果用 SQL 核过库里那一行(不看脚本的话): part-substitution-assessment@2.0.0 · enabled · steps 恰四步 [S1_inputs, S2_assess, S3_review, T_release] · 零处残留已退役的工具名 · declarations.tools=[] · 五条样例名与设计一致。 **Step 4(五案例 e2e)没跑,故本提交不声称任何跑通结果。** 卡在登录:KEYCLOAK_E2E_CLIENT_SECRET 与 KEYCLOAK_PROVISIONING_CLIENT_SECRET 指纹完全相同(已核),e2e 服务账号的 secret 从来没被真正配过,取 token 恒 401。 另一条路 JCAI_E2E_BEARER 解出来是真人的 token 且已过期 —— 试跑绝不派到用户 工作台,政策上不能用,技术上也用不了。 pnpm gates 退出码 0(2407 通过 / 34 跳过)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -93,14 +93,21 @@ describe('清单本身合规', () => {
|
|||||||
expect(launchProps).not.toContain('datasheets');
|
expect(launchProps).not.toContain('datasheets');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('五条样例:第一条满载走数据工单那条路,名字 ≤14 字,形状各不相同', () => {
|
/*
|
||||||
|
* 2026-08-29 Task 11:随新样例集换血(单数 `candidateInternalPn`,起单只剩两格)——
|
||||||
|
* 旧断言查的是已撤下的 `candidateInternalPns`(数组)/ `customerName`,这里同步核新形状:
|
||||||
|
* 第一条是「候选缺库」那条路(S1 会派数据工单),每条样例的入参恰是启动契约那两格,不多不少。
|
||||||
|
*/
|
||||||
|
it('五条样例:第一条候选缺库那条路,名字 ≤14 字,入参恰两格且各不相同', () => {
|
||||||
expect(manifest.samples).toHaveLength(5);
|
expect(manifest.samples).toHaveLength(5);
|
||||||
for (const s of manifest.samples) expect(s.name.length, s.name).toBeLessThanOrEqual(14);
|
for (const s of manifest.samples) expect(s.name.length, s.name).toBeLessThanOrEqual(14);
|
||||||
expect(new Set(manifest.samples.map((s) => JSON.stringify(s.input))).size).toBe(5);
|
expect(new Set(manifest.samples.map((s) => JSON.stringify(s.input))).size).toBe(5);
|
||||||
const first = manifest.samples[0]?.input as { candidateInternalPns?: string[] };
|
const first = manifest.samples[0]?.input as { originalInternalPn?: string; candidateInternalPn?: string };
|
||||||
expect(first.candidateInternalPns).toContain('XL2596S-5.0');
|
expect(first.originalInternalPn).toBe('LM2596S-5.0');
|
||||||
const newCustomer = manifest.samples.find((s) => (s.input as { customerName?: string }).customerName !== undefined);
|
expect(first.candidateInternalPn).toBe('XL2596S-5.0');
|
||||||
expect(newCustomer).toBeDefined();
|
for (const s of manifest.samples) {
|
||||||
|
expect(Object.keys(s.input as Record<string, unknown>).sort()).toEqual(['candidateInternalPn', 'originalInternalPn']);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -462,61 +462,21 @@ export const manifest: ActivityManifest = {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* ── 样例集:五条形状真不同(常备案例即验收案例)──
|
* ── 样例集:五条形状真不同(常备案例即验收案例)──
|
||||||
* 本任务不改样例(它们仍是旧九步 / 旧入参形状的素材,Task 3 重写案例模型时一并核对)。
|
* **2026-08-29 Task 11 换成新入参形状**(单数 `candidateInternalPn`,与 §4.1 的启动两参同步 ——
|
||||||
* 库里只播了 LM2596S-5.0 / LM2596S-ADJ 两颗(见 seed-mvp-data.ts),XL2596S-5.0 / CN-DCDC-32V / LM2596S-3.3 刻意不在库。
|
* 旧九步遗留的 `customerCode`/`requestDescription`/`candidateInternalPns`(数组)三键随之撤下,
|
||||||
|
* 起单表单本就只剩两格)。五条覆盖 S1 的五种入场局面:①候选缺库(新建类型那条路,S1 数据工单
|
||||||
|
* 三块都要动)②两颗都在库且同型(零卡直接进 S2)③两颗都在库但分属不同芯片类型(硬拦,
|
||||||
|
* 光凭其它格子填满绕不过去)④原 PN 缺库(候选在库)⑤两颗都缺库。
|
||||||
|
* 库里播了 LM2596S-5.0 / LM2596S-ADJ(DC-DC 降压稳压器,type_id=1)与 AMS1117-3.3
|
||||||
|
* (LDO 线性稳压器,type_id=2)三颗(见 seed-mvp-data.ts);XL2596S-5.0 / CN-DCDC-32V /
|
||||||
|
* LM2596S-3.3 刻意不在库。
|
||||||
*/
|
*/
|
||||||
samples: [
|
samples: [
|
||||||
{
|
{ name: '候选缺库·新建类型', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'XL2596S-5.0' }, expect: { ports: ['assessment-released'] } },
|
||||||
name: '候选缺库·派数据工单',
|
{ name: '都在库·零卡直分析', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'LM2596S-ADJ' }, expect: { ports: ['assessment-released'] } },
|
||||||
input: {
|
{ name: '类型不一致·硬拦', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'AMS1117-3.3' }, expect: { ports: ['assessment-released'] } },
|
||||||
customerCode: 'CU-0142',
|
{ name: '原PN缺库', input: { originalInternalPn: 'LM2596S-3.3', candidateInternalPn: 'LM2596S-ADJ' }, expect: { ports: ['assessment-released'] } },
|
||||||
requestDescription: '工业电源项目在用 LM2596S-5.0(TI,12V→5V),原厂 PCN 转 NRND,客户要求验证替代料;应用 12V→5V、工业温域、输入上限 40V',
|
{ name: '两颗都缺库', input: { originalInternalPn: 'CN-DCDC-32V', candidateInternalPn: 'XL2596S-5.0' }, expect: { ports: ['assessment-released'] } },
|
||||||
originalInternalPn: 'LM2596S-5.0',
|
|
||||||
candidateInternalPns: ['XL2596S-5.0', 'LM2596S-ADJ'],
|
|
||||||
},
|
|
||||||
expect: { ports: ['assessment-released'] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '都在库·常路',
|
|
||||||
input: {
|
|
||||||
customerCode: 'CU-0142',
|
|
||||||
requestDescription: 'LM2596S-5.0 转 NRND,客户先评估同族可调版 LM2596S-ADJ 能否替代(愿意改外围);应用 12V→5V、工业温域',
|
|
||||||
originalInternalPn: 'LM2596S-5.0',
|
|
||||||
candidateInternalPns: ['LM2596S-ADJ'],
|
|
||||||
},
|
|
||||||
expect: { ports: ['assessment-released'] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '原PN缺库·硬阻塞',
|
|
||||||
input: {
|
|
||||||
customerCode: 'CU-0142',
|
|
||||||
requestDescription: '客户另一块板在用 LM2596S-3.3(3.3V 固定版),同批 PCN,要求评估 LM2596S-ADJ 替代;应用 12V→3.3V',
|
|
||||||
originalInternalPn: 'LM2596S-3.3',
|
|
||||||
candidateInternalPns: ['LM2596S-ADJ'],
|
|
||||||
},
|
|
||||||
expect: { ports: ['assessment-released'] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '三候选·含硬约束不推荐',
|
|
||||||
input: {
|
|
||||||
customerCode: 'CU-0142',
|
|
||||||
requestDescription: '工业电源 12V→5V,输入上限 40V,工业温域;三个候选:XL2596S-5.0、LM2596S-ADJ、国产 CN-DCDC-32V(输入上限只有 32V)',
|
|
||||||
originalInternalPn: 'LM2596S-5.0',
|
|
||||||
candidateInternalPns: ['XL2596S-5.0', 'LM2596S-ADJ', 'CN-DCDC-32V'],
|
|
||||||
},
|
|
||||||
expect: { ports: ['assessment-released'] },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '新客户·先建档',
|
|
||||||
input: {
|
|
||||||
customerCode: 'CU-9001',
|
|
||||||
customerName: '深圳恒星电源有限公司',
|
|
||||||
requestDescription: '新客户口述:工业电源 12V→5V 在用 LM2596S-5.0,想评估 LM2596S-ADJ',
|
|
||||||
originalInternalPn: 'LM2596S-5.0',
|
|
||||||
candidateInternalPns: ['LM2596S-ADJ'],
|
|
||||||
},
|
|
||||||
expect: { ports: ['assessment-released'] },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
entry: { file: 'index.js', checksum: 'dev-entry-part-substitution-assessment' },
|
entry: { file: 'index.js', checksum: 'dev-entry-part-substitution-assessment' },
|
||||||
|
|||||||
+237
-171
@@ -53,17 +53,20 @@ const LAND_MASTER = process.env.JCAI_E2E_LAND_MASTER === '1';
|
|||||||
const TOKEN_REFRESH_MS = 4 * 60 * 1000;
|
const TOKEN_REFRESH_MS = 4 * 60 * 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标准化参数子表 `parts.parameter` 的行(w05 专用)—— 按 [参数名, 值, 单位, 约束, 说明]
|
* 标准化参数子表 `parts.parameter` 的行(w05 专用)—— 按 [参数名, 值, 单位, 说明]
|
||||||
* 五元组批量转成保存载荷的行对象;数值照抄各案例原先写在 `normalized_parameter_set`
|
* 四元组批量转成保存载荷的行对象。
|
||||||
* 那个文本列里的那几行,一个数都不改(2026-08-27 结构改:参数集拆成独立子表)。
|
*
|
||||||
|
* **2026-08-29 Task 11 四元组(从前是五元组)**:`constraint_type` 已随四步重写搬去
|
||||||
|
* `parts.type_param`(约束按类型标一次,不再逐颗料各标一遍 —— 见 manifest.ts 该字段的头注)。
|
||||||
|
* `parts.parameter` 现在只有 internal_pn / param_name / param_value / param_unit / param_note
|
||||||
|
* 五列,没有 `constraint_type` 这一列了;继续按老的五元组喂会把一个物理表没有的列塞进 patch。
|
||||||
*/
|
*/
|
||||||
function paramRows(internalPn, rows) {
|
function paramRows(internalPn, rows) {
|
||||||
return rows.map(([param_name, param_value, param_unit, constraint_type, param_note]) => ({
|
return rows.map(([param_name, param_value, param_unit, param_note]) => ({
|
||||||
internal_pn: internalPn,
|
internal_pn: internalPn,
|
||||||
param_name,
|
param_name,
|
||||||
param_value,
|
param_value,
|
||||||
param_unit,
|
param_unit,
|
||||||
constraint_type,
|
|
||||||
param_note,
|
param_note,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -543,235 +546,238 @@ const SUITES = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* ACT-WORK-05 差异评估 —— 1 数据(条件,P-08 / P-10 各一张)+ 1 执行 + 2 验收(审核走决策卡循环、放行终局)。
|
* ACT-WORK-05 差异评估 —— **2026-08-29 Task 11 随四步重写整体换掉**(spec
|
||||||
* 库里只播了 LM2596S-5.0 / LM2596S-ADJ:XL2596S-5.0、CN-DCDC-32V、LM2596S-3.3 刻意不在,
|
* `docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md`)。旧套件是九步 /
|
||||||
* 「候选缺库 / 原 PN 缺库 / 硬约束不推荐」三条路靠它们。e2e 关落真库,主数据不落正式表,案例可反复跑。
|
* 两枚工具 / 三张数据工单(P-08 主数据 + P-10 资料 + 评估输入清单)那个模型的素材,
|
||||||
|
* 新模型只有一张数据工单(`input-supplement`,三块竖排:要评估什么 / 芯片类型核心参数
|
||||||
|
* 字段 / 参数值,合在一张卡上、一次保存四张表)+ 一次 AI 分析(S2,零人环)+
|
||||||
|
* 一张审核决策卡(S3,可循环)+ 一张终局放行验收卡(T_release)。
|
||||||
|
* 库里播了 LM2596S-5.0 / LM2596S-ADJ(DC-DC 降压稳压器 type_id=1)与
|
||||||
|
* AMS1117-3.3(LDO 线性稳压器 type_id=2)三颗;XL2596S-5.0 / CN-DCDC-32V / LM2596S-3.3
|
||||||
|
* 刻意不在库。e2e 关落真库,主数据不落正式表,案例可反复跑。
|
||||||
|
*
|
||||||
|
* **两处平台机制这一次是第一个用到的调用方,`runCase`/`answerDecisions` 主循环里
|
||||||
|
* 补了对应支持**(其余八套件都没用过,行为不变):
|
||||||
|
* ① **人工验收清单**(2026-08-27 用户定,`PlanStep.acceptanceChecklist`):S3 审核卡
|
||||||
|
* 与 T_release 终局验收卡都带 `checklist`,选「通过」/`verdict:'pass'` 必须
|
||||||
|
* `checked ⊇ checklist`,不然回传吃 `checklist-incomplete`(400)。清单原文由
|
||||||
|
* 卡自己带下来(`card.checklist` / `workOrders[].checklist`),代答脚本原样照抄
|
||||||
|
* 回传,不必知道清单具体写的是哪几句话。
|
||||||
|
* ② **零数据卡直分析**(样例 1):S1 的 `doneWhen` 一开局就成立,`auto.do` 的
|
||||||
|
* `card` 那一笔永不触发 —— 断言机制是 `expectNoDataCard`(见 `stage` 定义处)。
|
||||||
*/
|
*/
|
||||||
w05: {
|
w05: {
|
||||||
activityId: 'part-substitution-assessment',
|
activityId: 'part-substitution-assessment',
|
||||||
address: 'sub_eval.input_gap',
|
address: 'parts.profile',
|
||||||
blankCol: 'supplement',
|
blankCol: 'brand_name',
|
||||||
stages: [
|
stages: [
|
||||||
{
|
{
|
||||||
// 这张卡摆两张表(表 1 主记录 + 表 2 标准化参数子表)—— 主表照旧,子表挂在 extraTables 里
|
// 唯一的数据工单:一张卡摆三块内容,写向四张表(主表 parts.profile,
|
||||||
// 加性表达(见 buildEdits 顶注),保存时两张表的行一次并进同一个 `tables` 载荷。
|
// extraTables 里另外三张)——四张表随同一次「保存」一并发出,不分次交。
|
||||||
match: /数据工单 · 芯片主数据创建/,
|
match: /差异评估的输入还差几格/,
|
||||||
address: 'parts.profile',
|
address: 'parts.profile',
|
||||||
rowKey: 'internal_pn',
|
rowKey: 'internal_pn',
|
||||||
blankCol: 'brand_name',
|
blankCol: 'brand_name',
|
||||||
expectGateBlock: true,
|
expectGateBlock: true,
|
||||||
// 数据工单确认即入库(2026-08-27 用户定)—— 交卷那一刻这张卡的表就该进正式表
|
|
||||||
landsOnConfirm: true,
|
|
||||||
edits: [],
|
edits: [],
|
||||||
// 案例没点名的料号兜底(不该走到这里 —— 每个案例都在 stageEdits 里逐颗给了)
|
|
||||||
fillAllBlank: {
|
|
||||||
brand_name: '兜底品牌',
|
|
||||||
specification_description: '兜底规格(案例没给参数集)',
|
|
||||||
package: 'TO-263-5',
|
|
||||||
lifecycle_status: 'active',
|
|
||||||
},
|
|
||||||
extraTables: [
|
extraTables: [
|
||||||
|
/*
|
||||||
|
* `parts.chip_type` **没有任何 `data-edit` 绑定**(manifest.ts / case-model.ts
|
||||||
|
* 头注明写:新建类型时人在 `parts.type_param` 那张表加行,`parts.chip_type` 那
|
||||||
|
* 张字典表本身没有编辑面 —— `ViewContainer.addRow` 靠克隆末行实现「加一行」,
|
||||||
|
* 而它在任何一单里都是从零行起,没有可克隆的样子)。写的授权面是**台账声明**
|
||||||
|
* 不是「当下卡上有没有绑定」(`writesOf` 只认 `declarations.ledger`),所以这条
|
||||||
|
* 写入走得通 —— 只是脚本代脚本走 API,不是真人代替点了一个并不存在的按钮。
|
||||||
|
* 只有样例 0(候选缺库·新建类型)会喂 `edits`,其余案例这里留空、这张表不写。
|
||||||
|
*/
|
||||||
|
{ address: 'parts.chip_type', rowKey: 'type_id', blankCol: 'type_name', edits: [] },
|
||||||
{
|
{
|
||||||
// 主键是 (internal_pn, param_name) 复合列 —— 两颗料同名参数(如都有「工作温度」)
|
// 复合主键 (type_id, param_name) —— 同一类型两个字段不能靠单列区分。
|
||||||
|
address: 'parts.type_param',
|
||||||
|
rowKey: ['type_id', 'param_name'],
|
||||||
|
blankCol: 'param_desc',
|
||||||
|
edits: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 复合主键 (internal_pn, param_name) —— 两颗料同名参数(如都有「工作温度」)
|
||||||
// 不这样区分,rows.find 会永远命中第一条同名行、把不同料的值互相顶掉。
|
// 不这样区分,rows.find 会永远命中第一条同名行、把不同料的值互相顶掉。
|
||||||
address: 'parts.parameter',
|
address: 'parts.parameter',
|
||||||
rowKey: ['internal_pn', 'param_name'],
|
rowKey: ['internal_pn', 'param_name'],
|
||||||
blankCol: 'param_value',
|
blankCol: 'param_value',
|
||||||
edits: [],
|
edits: [],
|
||||||
// 案例没点名的参数行兜底(模型骨架只给了 internal_pn+param_name、案例没接住时才会用到)
|
|
||||||
fillAllBlank: { param_value: '未披露', constraint_type: 'hard', param_note: '兜底(案例没给这一条)' },
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
match: /数据工单 · 原厂技术资料创建/,
|
|
||||||
address: 'parts.document',
|
|
||||||
rowKey: 'internal_pn',
|
|
||||||
blankCol: 'document_title',
|
|
||||||
expectGateBlock: true,
|
|
||||||
landsOnConfirm: true,
|
|
||||||
edits: [],
|
|
||||||
fillAllBlank: {
|
|
||||||
doc_seq: '01',
|
|
||||||
document_type: 'datasheet',
|
|
||||||
document_title: 'Datasheet(e2e 兜底)',
|
|
||||||
document_version: 'Rev A',
|
|
||||||
document_ref: 'https://example.com/datasheet.pdf',
|
|
||||||
citation_locator: 'p.1',
|
|
||||||
document_status: 'valid',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /评估输入与资料补齐确认/,
|
|
||||||
address: 'sub_eval.input_gap',
|
|
||||||
rowKey: 'item_key',
|
|
||||||
blankCol: 'supplement',
|
|
||||||
expectGateBlock: true,
|
|
||||||
edits: [
|
|
||||||
{ item_key: 'application_scenario', supplement: '12V→5V、工业温域(-40~85℃)、输入上限 40V' },
|
|
||||||
{ item_key: 'bom_position', supplement: 'U3 电源位' },
|
|
||||||
],
|
|
||||||
// 其余 gap 行(原厂未披露的参数)统一「留 UNKNOWN」—— 补不了也是合法答案
|
|
||||||
fillWhere: { column: 'status', equals: 'gap', values: { supplement: '原厂未披露,留 UNKNOWN 并挂验证任务' } },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
cases: [
|
cases: [
|
||||||
{
|
{
|
||||||
sample: 0,
|
sample: 0,
|
||||||
name: '候选缺库 · XL2596S-5.0 自动派数据工单(P-08 + P-10)· 审核先点名驳回再通过',
|
name: '候选缺库 · XL2596S-5.0 新建芯片类型(type_id=3,字段清单沿用原型口径)入库,'
|
||||||
// 决策卡序(2026-08-27 起缺库不再问人,数据工单自动派):审核第一张点名意见驳回;第二张通过
|
+ '原 PN 一并转挂新类型才够得上 distinct=1 · 审核先点名意见驳回再通过',
|
||||||
decideSeq: ['custom:使能阈值那条 UNKNOWN 的去向请写明验证任务号与测试条件', 'approve'],
|
decideSeq: ['custom:使能阈值那条 UNKNOWN 的去向请写明验证任务号与测试条件', 'approve'],
|
||||||
expectDecision: true,
|
expectDecision: true,
|
||||||
expectPort: 'assessment-released',
|
expectPort: 'assessment-released',
|
||||||
|
// 全套只该有一次模型调用(S2 唯一一次 analyze)—— S3 审核循环走的是纯投影重算,不重问模型
|
||||||
|
expectAnalyzeCalls: 1,
|
||||||
stageEdits: {
|
stageEdits: {
|
||||||
|
'parts.chip_type': [
|
||||||
|
{
|
||||||
|
type_id: '3',
|
||||||
|
type_name: 'DC-DC 降压稳压器(评估自定义)',
|
||||||
|
description: '本次评估自定义的降压转换器类型,核心参数字段清单沿用原类型口径,供原 PN 与候选料共用比对。',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 字段名 / 说明 / 约束 / 单位逐字照抄 type_id=1 的字典行(见 seed-mvp-data.ts)——
|
||||||
|
// 换个类型号但比对维度不变,原 PN 既有的参数值才能按同名继续 lookup 到。
|
||||||
|
'parts.type_param': [
|
||||||
|
{ type_id: '3', param_name: '输入电压范围', param_desc: '芯片允许的输入直流电压区间。候选料的上限低于应用输入上限就是硬伤,烧的是芯片。', constraint_type: 'hard', param_unit: 'V' },
|
||||||
|
{ type_id: '3', param_name: '输出电压', param_desc: '固定版给定值;可调版给可调区间,需外围反馈电阻网络设定。固定换可调要动外围。', constraint_type: 'hard', param_unit: 'V' },
|
||||||
|
{ type_id: '3', param_name: '输出电流', param_desc: '满载持续输出能力。低于原料即降额,负载重时会掉压或过热保护。', constraint_type: 'hard', param_unit: 'A' },
|
||||||
|
{ type_id: '3', param_name: '开关频率', param_desc: '决定外围电感与输出电容的取值。频率变了外围多半要跟着换,不是换颗料就完。', constraint_type: 'soft', param_unit: 'kHz' },
|
||||||
|
{ type_id: '3', param_name: '静态电流', param_desc: '空载待机时自身消耗。电池供电场景敏感,市电场景通常不敏感。', constraint_type: 'soft', param_unit: 'mA' },
|
||||||
|
{ type_id: '3', param_name: '工作温度', param_desc: '保证参数指标的环境温度区间。工业温域窄于应用要求就是硬伤。', constraint_type: 'hard', param_unit: '℃' },
|
||||||
|
{ type_id: '3', param_name: '使能阈值', param_desc: 'ON/OFF 引脚的开关门限。上游用 MCU 电平直驱时,阈值对不上会关不掉或开不起来。', constraint_type: 'hard', param_unit: 'V' },
|
||||||
|
],
|
||||||
'parts.profile': [
|
'parts.profile': [
|
||||||
|
// 原 PN 转挂新类型 —— distinct(chip_type_id) 要求两颗归同一个值,新建类型时
|
||||||
|
// 两颗都得落在它上面,不是只给候选料一颗挂新号。
|
||||||
|
{ internal_pn: 'LM2596S-5.0', chip_type_id: '3' },
|
||||||
{
|
{
|
||||||
internal_pn: 'XL2596S-5.0',
|
internal_pn: 'XL2596S-5.0',
|
||||||
|
chip_type_id: '3',
|
||||||
brand_name: 'XLSEMI',
|
brand_name: 'XLSEMI',
|
||||||
manufacturer_name: 'XLSEMI',
|
manufacturer_name: 'XLSEMI',
|
||||||
specification_description: '降压型 DCDC 稳压器,3A,固定 5.0V,150kHz',
|
specification_description: '降压型 DCDC 稳压器,3A,固定 5.0V,150kHz',
|
||||||
package: 'TO-263-5',
|
|
||||||
process_technology: 'BCD',
|
|
||||||
lifecycle_status: 'active',
|
lifecycle_status: 'active',
|
||||||
datasheet_ref: 'https://www.xlsemi.com/datasheet/XL2596S.pdf',
|
datasheet_ref: 'https://www.xlsemi.com/datasheet/XL2596S.pdf',
|
||||||
datasheet_version: 'Rev C',
|
datasheet_version: 'Rev C',
|
||||||
application_scenarios: '工业电源;消费电源',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
// LM2596S-5.0 的既有参数值按同名 lookup 自动带过来,不必在这里重填;
|
||||||
|
// 只需交代候选料 XL2596S-5.0 那一份。
|
||||||
'parts.parameter': paramRows('XL2596S-5.0', [
|
'parts.parameter': paramRows('XL2596S-5.0', [
|
||||||
['输入电压范围', '4.5~40', 'V', 'hard', '客户上限 40V'],
|
['输入电压范围', '4.5~40', 'V', '客户上限 40V'],
|
||||||
['输出电压', '5.0', 'V', 'hard', '固定版'],
|
['输出电压', '5.0', 'V', '固定版'],
|
||||||
['输出电流', '3', 'A', 'hard', '满载能力'],
|
['输出电流', '3', 'A', '满载能力'],
|
||||||
['开关频率', '150', 'kHz', 'hard', '影响外围'],
|
['开关频率', '150', 'kHz', '影响外围'],
|
||||||
['静态电流', '4.2', 'mA', 'soft', '待机功耗'],
|
['静态电流', '4.2', 'mA', '待机功耗'],
|
||||||
['工作温度', '-40~+125', '℃', 'hard', '工业温域'],
|
['工作温度', '-40~+125', '℃', '工业温域'],
|
||||||
['使能阈值', '未披露', 'V', 'hard', '原厂未给'],
|
['使能阈值', '未披露', 'V', '原厂未给'],
|
||||||
]),
|
]),
|
||||||
'parts.document': [
|
|
||||||
{
|
|
||||||
internal_pn: 'XL2596S-5.0',
|
|
||||||
doc_seq: '01',
|
|
||||||
document_type: 'datasheet',
|
|
||||||
brand_name: 'XLSEMI',
|
|
||||||
document_title: 'XL2596S 150KHz 3A Buck DC/DC Converter',
|
|
||||||
document_version: 'Rev C',
|
|
||||||
document_ref: 'https://www.xlsemi.com/datasheet/XL2596S.pdf',
|
|
||||||
citation_locator: 'p.3 Electrical Characteristics',
|
|
||||||
document_status: 'valid',
|
|
||||||
},
|
},
|
||||||
],
|
// 使能阈值未披露 → UNKNOWN → 有条件推荐(规则算的,不是模型说的)
|
||||||
},
|
|
||||||
// 使能阈值未披露 → UNKNOWN → 条件推荐(工具算的,不是模型说的)
|
|
||||||
expectRevised: { address: 'substitution.relation', where: 'candidate_internal_pn', equals: 'XL2596S-5.0', column: 'recommendation_level', contains: 'conditional' },
|
expectRevised: { address: 'substitution.relation', where: 'candidate_internal_pn', equals: 'XL2596S-5.0', column: 'recommendation_level', contains: 'conditional' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sample: 1,
|
sample: 1,
|
||||||
name: '都在库 · 常路 · 可调版 → 备选 · 审核一次通过',
|
name: '都在库 · 两颗同型且齐备 · 零数据卡直接进 S2 分析 · 审核一次通过',
|
||||||
|
// S1 的 doneWhen 起手就成立(两颗都在库、同型、参数不缺)—— 断言一张数据工单都不发
|
||||||
|
expectNoDataCard: true,
|
||||||
decide: 'approve',
|
decide: 'approve',
|
||||||
expectPort: 'assessment-released',
|
expectPort: 'assessment-released',
|
||||||
expectRevised: { address: 'substitution.relation', where: 'candidate_internal_pn', equals: 'LM2596S-ADJ', column: 'recommendation_level', contains: 'alternate' },
|
expectAnalyzeCalls: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sample: 2,
|
sample: 2,
|
||||||
name: '原 PN 缺库 · 硬阻塞建档(不问人)· 审核通过',
|
name: '两颗都在库但分属不同芯片类型 · 硬拦(填别的格子绕不过去,只有改类型才行)· 审核通过',
|
||||||
|
// gate_reason 要点名「不是同一个芯片类型」——数据卡一出现就查这句话,latch 住
|
||||||
|
expectGateReason: /芯片类型/,
|
||||||
|
// 唯一的修复是把其中一颗转挂到另一颗的类型上;缺的那一个新必填参数一并补上,
|
||||||
|
// 不改类型光填别的格子过不去(S1 的 doneWhen/distinct 与提交闸的必填检查不是同一件事——
|
||||||
|
// 提交闸不查 distinct,这条路真正的硬拦在 S2 的入场哨兵,详见任务报告)。
|
||||||
|
stageEdits: {
|
||||||
|
'parts.profile': [{ internal_pn: 'LM2596S-5.0', chip_type_id: '2' }],
|
||||||
|
// 转去 LDO 类型后,LM2596S-5.0 多出一条它从没记过的必填参数「压差」
|
||||||
|
'parts.parameter': [
|
||||||
|
{ internal_pn: 'LM2596S-5.0', param_name: '压差', param_value: '0.4', param_unit: 'V', param_note: '满载时(本次评估按 LDO 口径补充,DC-DC 拓扑本无此规格)' },
|
||||||
|
],
|
||||||
|
},
|
||||||
decide: 'approve',
|
decide: 'approve',
|
||||||
expectPort: 'assessment-released',
|
expectPort: 'assessment-released',
|
||||||
|
expectAnalyzeCalls: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sample: 3,
|
||||||
|
name: '原 PN 缺库(候选在库)· 自动派数据工单建档 · 审核通过',
|
||||||
|
decide: 'approve',
|
||||||
|
expectPort: 'assessment-released',
|
||||||
|
expectAnalyzeCalls: 1,
|
||||||
stageEdits: {
|
stageEdits: {
|
||||||
'parts.profile': [
|
'parts.profile': [
|
||||||
{
|
{
|
||||||
internal_pn: 'LM2596S-3.3',
|
internal_pn: 'LM2596S-3.3',
|
||||||
|
chip_type_id: '1',
|
||||||
brand_name: 'TI',
|
brand_name: 'TI',
|
||||||
manufacturer_name: 'Texas Instruments',
|
manufacturer_name: 'Texas Instruments',
|
||||||
specification_description: '降压型 DCDC 稳压器,3A,固定 3.3V,150kHz',
|
specification_description: '降压型 DCDC 稳压器,3A,固定 3.3V,150kHz',
|
||||||
package: 'TO-263-5',
|
|
||||||
process_technology: 'BCD',
|
|
||||||
lifecycle_status: 'nrnd',
|
lifecycle_status: 'nrnd',
|
||||||
datasheet_ref: 'https://www.ti.com/lit/ds/symlink/lm2596.pdf',
|
datasheet_ref: 'https://www.ti.com/lit/ds/symlink/lm2596.pdf',
|
||||||
datasheet_version: 'Rev H (2023-09)',
|
datasheet_version: 'Rev H (2023-09)',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'parts.parameter': paramRows('LM2596S-3.3', [
|
'parts.parameter': paramRows('LM2596S-3.3', [
|
||||||
['输入电压范围', '4.5~40', 'V', 'hard', '客户上限 40V'],
|
['输入电压范围', '4.5~40', 'V', '客户上限 40V'],
|
||||||
['输出电压', '3.3', 'V', 'hard', '固定版'],
|
['输出电压', '3.3', 'V', '固定版'],
|
||||||
['输出电流', '3', 'A', 'hard', '满载能力'],
|
['输出电流', '3', 'A', '满载能力'],
|
||||||
['开关频率', '150', 'kHz', 'hard', '影响外围'],
|
['开关频率', '150', 'kHz', '影响外围'],
|
||||||
['静态电流', '5', 'mA', 'soft', '待机功耗'],
|
['静态电流', '5', 'mA', '待机功耗'],
|
||||||
['工作温度', '-40~+85', '℃', 'hard', '工业温域'],
|
['工作温度', '-40~+85', '℃', '工业温域'],
|
||||||
['使能阈值', '1.4', 'V', 'hard', 'ON/OFF 阈值'],
|
['使能阈值', '1.4', 'V', 'ON/OFF 阈值'],
|
||||||
]),
|
]),
|
||||||
'parts.document': [
|
|
||||||
{
|
|
||||||
internal_pn: 'LM2596S-3.3',
|
|
||||||
doc_seq: '01',
|
|
||||||
document_type: 'datasheet',
|
|
||||||
brand_name: 'TI',
|
|
||||||
document_title: 'LM2596 SIMPLE SWITCHER Power Converter',
|
|
||||||
document_version: 'Rev H (2023-09)',
|
|
||||||
document_ref: 'https://www.ti.com/lit/ds/symlink/lm2596.pdf',
|
|
||||||
citation_locator: '§6.5 Electrical Characteristics(3.3V 列)',
|
|
||||||
document_status: 'valid',
|
|
||||||
},
|
},
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sample: 3,
|
|
||||||
name: '三候选 · 两颗缺库一律自动派单 · 国产 32V 硬约束不推荐',
|
|
||||||
decideSeq: ['approve'],
|
|
||||||
expectDecision: true,
|
|
||||||
// 缺库不再问人(2026-08-27 用户定):这一例一张「查无记录」决策卡都不该出现
|
|
||||||
expectAsks: [{ re: /查无记录/, count: 0, max: 0 }],
|
|
||||||
expectPort: 'assessment-released',
|
|
||||||
stageEdits: {
|
|
||||||
'parts.profile': [
|
|
||||||
{
|
|
||||||
internal_pn: 'XL2596S-5.0',
|
|
||||||
brand_name: 'XLSEMI',
|
|
||||||
specification_description: '降压型 DCDC 稳压器,3A,固定 5.0V,150kHz',
|
|
||||||
package: 'TO-263-5',
|
|
||||||
lifecycle_status: 'active',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
internal_pn: 'CN-DCDC-32V',
|
|
||||||
brand_name: '国产某厂',
|
|
||||||
specification_description: '降压型 DCDC,3A,固定 5.0V,输入上限 32V',
|
|
||||||
package: 'TO-263-5',
|
|
||||||
lifecycle_status: 'active',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'parts.parameter': [
|
|
||||||
...paramRows('XL2596S-5.0', [
|
|
||||||
['输入电压范围', '4.5~40', 'V', 'hard', '客户上限 40V'],
|
|
||||||
['输出电压', '5.0', 'V', 'hard', '固定版'],
|
|
||||||
['输出电流', '3', 'A', 'hard', '满载能力'],
|
|
||||||
['开关频率', '150', 'kHz', 'hard', '影响外围'],
|
|
||||||
['静态电流', '4.2', 'mA', 'soft', '待机功耗'],
|
|
||||||
['工作温度', '-40~+125', '℃', 'hard', '工业温域'],
|
|
||||||
['使能阈值', '未披露', 'V', 'hard', '原厂未给'],
|
|
||||||
]),
|
|
||||||
...paramRows('CN-DCDC-32V', [
|
|
||||||
['输入电压范围', '4.5~32', 'V', 'hard', '上限 32V'],
|
|
||||||
['输出电压', '5.0', 'V', 'hard', '固定版'],
|
|
||||||
['输出电流', '3', 'A', 'hard', '满载能力'],
|
|
||||||
['开关频率', '150', 'kHz', 'hard', '影响外围'],
|
|
||||||
['静态电流', '6', 'mA', 'soft', '待机功耗'],
|
|
||||||
['工作温度', '-40~+85', '℃', 'hard', '工业温域'],
|
|
||||||
['使能阈值', '1.3', 'V', 'hard', 'ON/OFF 阈值'],
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
'parts.document': [
|
|
||||||
{ internal_pn: 'XL2596S-5.0', doc_seq: '01', document_type: 'datasheet', document_title: 'XL2596S Datasheet', document_version: 'Rev C', document_ref: 'https://www.xlsemi.com/datasheet/XL2596S.pdf', citation_locator: 'p.3', document_status: 'valid' },
|
|
||||||
{ internal_pn: 'CN-DCDC-32V', doc_seq: '01', document_type: 'datasheet', document_title: 'CN-DCDC-32V Datasheet', document_version: 'V1.0', document_ref: 'https://example.com/cn-dcdc-32v.pdf', citation_locator: 'p.2', document_status: 'valid' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
expectRevised: { address: 'substitution.relation', where: 'candidate_internal_pn', equals: 'CN-DCDC-32V', column: 'recommendation_level', contains: 'not_recommended' },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sample: 4,
|
sample: 4,
|
||||||
name: '新客户 · 录入卡先建档 · 审核通过',
|
name: '两颗都缺库 · 自动派数据工单各自建档(原 PN 32V 上限 · 候选 40V 盖住它)· 审核通过',
|
||||||
entryValues: { name: '深圳恒星电源有限公司', industry: '工业电源', region: '广东' },
|
|
||||||
decide: 'approve',
|
decide: 'approve',
|
||||||
expectPort: 'assessment-released',
|
expectPort: 'assessment-released',
|
||||||
|
expectAnalyzeCalls: 1,
|
||||||
|
stageEdits: {
|
||||||
|
'parts.profile': [
|
||||||
|
{
|
||||||
|
internal_pn: 'CN-DCDC-32V',
|
||||||
|
chip_type_id: '1',
|
||||||
|
brand_name: '国产某厂',
|
||||||
|
manufacturer_name: '国产某厂',
|
||||||
|
specification_description: '降压型 DCDC 稳压器,3A,固定 5.0V,150kHz,输入上限 32V',
|
||||||
|
lifecycle_status: 'active',
|
||||||
|
datasheet_ref: 'https://example.invalid/cn-dcdc-32v.pdf',
|
||||||
|
datasheet_version: 'V1.0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
internal_pn: 'XL2596S-5.0',
|
||||||
|
chip_type_id: '1',
|
||||||
|
brand_name: 'XLSEMI',
|
||||||
|
manufacturer_name: 'XLSEMI',
|
||||||
|
specification_description: '降压型 DCDC 稳压器,3A,固定 5.0V,150kHz',
|
||||||
|
lifecycle_status: 'active',
|
||||||
|
datasheet_ref: 'https://www.xlsemi.com/datasheet/XL2596S.pdf',
|
||||||
|
datasheet_version: 'Rev C',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'parts.parameter': [
|
||||||
|
...paramRows('CN-DCDC-32V', [
|
||||||
|
['输入电压范围', '4.5~32', 'V', '上限 32V'],
|
||||||
|
['输出电压', '5.0', 'V', '固定版'],
|
||||||
|
['输出电流', '3', 'A', '满载能力'],
|
||||||
|
['开关频率', '150', 'kHz', '影响外围'],
|
||||||
|
['静态电流', '6', 'mA', '待机功耗'],
|
||||||
|
['工作温度', '-40~+85', '℃', '工业温域'],
|
||||||
|
['使能阈值', '1.3', 'V', 'ON/OFF 阈值'],
|
||||||
|
]),
|
||||||
|
...paramRows('XL2596S-5.0', [
|
||||||
|
['输入电压范围', '4.5~40', 'V', '客户上限 40V'],
|
||||||
|
['输出电压', '5.0', 'V', '固定版'],
|
||||||
|
['输出电流', '3', 'A', '满载能力'],
|
||||||
|
['开关频率', '150', 'kHz', '影响外围'],
|
||||||
|
['静态电流', '4.2', 'mA', '待机功耗'],
|
||||||
|
['工作温度', '-40~+125', '℃', '工业温域'],
|
||||||
|
['使能阈值', '未披露', 'V', '原厂未给'],
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// 原 PN(CN-DCDC-32V)输入上限只有 32V,候选 XL2596S-5.0 上限 40V 盖住原 PN —— 不该拦;
|
||||||
|
// 真正的硬约束不符落在别处案例,这条只钉「两颗都缺库仍能走完全程」,故不设 expectRevised。
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -843,6 +849,18 @@ function rejectedCount(trail) {
|
|||||||
return trail.filter((t) => t.kind === 'action-rejected' || /行动被拒|被拒[::]|拒绝执行/.test(t.title ?? '')).length;
|
return trail.filter((t) => t.kind === 'action-rejected' || /行动被拒|被拒[::]|拒绝执行/.test(t.title ?? '')).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* **AI 出场次数**(w05 专用,ADR-014 的 `analyze` 那一笔)—— 轨迹里数
|
||||||
|
* 「已把 N 条参数交给模型逐条判定,拿回 M 条结论」这句话(`auto-runner.ts` 的
|
||||||
|
* `kind:'observation'` 留痕,字面原样)。W05 全程零 agent,唯一一次模型调用长在 S2;
|
||||||
|
* 「行动被拒 0」这条验收线对没有 agent 决策的确定性步骤活动自动成立(没有行动可拒),
|
||||||
|
* 所以这里改守的是这个数 —— 它才是这次真正防回归的那条线(S3 审核循环 / T_release
|
||||||
|
* 重做都不该让它超过 1,S2 的入场哨兵 `s_inputs_ready` 也不该被绕过去多算一次)。
|
||||||
|
*/
|
||||||
|
function analyzeCallCount(trail) {
|
||||||
|
return trail.filter((t) => /已把 \d+ 条参数交给模型逐条判定/.test(t.title ?? '')).length;
|
||||||
|
}
|
||||||
|
|
||||||
/** 草稿层的行(带主键),按台账地址取 */
|
/** 草稿层的行(带主键),按台账地址取 */
|
||||||
function draftRows(drafts, address) {
|
function draftRows(drafts, address) {
|
||||||
return (drafts ?? []).filter((d) => d.address === address).map((d) => ({ ...d.key, ...d.patch }));
|
return (drafts ?? []).filter((d) => d.address === address).map((d) => ({ ...d.key, ...d.patch }));
|
||||||
@@ -916,6 +934,14 @@ async function expectGateBlocks(cardId, taskId, stage) {
|
|||||||
*
|
*
|
||||||
* 选项按案例声明的 value 认;认不上退「推荐项 → 第一项」——
|
* 选项按案例声明的 value 认;认不上退「推荐项 → 第一项」——
|
||||||
* 代答脚本不该因为模型换了个选项名就判活动失败。
|
* 代答脚本不该因为模型换了个选项名就判活动失败。
|
||||||
|
*
|
||||||
|
* **人工验收清单**(2026-08-27 用户定,W05 的 S3 审核卡第一个用到这条机制):
|
||||||
|
* 审核卡(`variant:'review'`)可能带 `card.checklist` ——选**通过**那一项时必须
|
||||||
|
* `checked ⊇ checklist`,否则回传吃 `checklist-incomplete`(400)。这里**原样照抄**
|
||||||
|
* `card.checklist` 回传(不必知道清单具体写的是哪几句话,清单原文由卡自己带下来);
|
||||||
|
* 只在选中的是「通过」那个值时才带(驳回/自定义意见不需要点全清单,与服务端
|
||||||
|
* `reviewApproveValue` 同一条判法:优先 value==='approve',否则退回推荐项、再退第一项)。
|
||||||
|
* 没有 `checklist` 的卡(其余八套件)`checked` 不出现在载荷里,行为与从前一字不差。
|
||||||
*/
|
*/
|
||||||
async function answerDecisions(taskId, kase, answered) {
|
async function answerDecisions(taskId, kase, answered) {
|
||||||
const { body } = await api(`/api/desk/tasks/${taskId}`);
|
const { body } = await api(`/api/desk/tasks/${taskId}`);
|
||||||
@@ -934,14 +960,17 @@ async function answerDecisions(taskId, kase, answered) {
|
|||||||
const picked = custom
|
const picked = custom
|
||||||
? { value: want, label: `意见:${want.slice(7)}` }
|
? { value: want, label: `意见:${want.slice(7)}` }
|
||||||
: (options.find((o) => o.value === want) ?? options.find((o) => o.recommended === true) ?? options[0]);
|
: (options.find((o) => o.value === want) ?? options.find((o) => o.recommended === true) ?? options[0]);
|
||||||
await api('/api/desk/chat-cards/reply', {
|
const approveValue = (options.find((o) => o.value === 'approve') ?? options.find((o) => o.recommended === true) ?? options[0])?.value;
|
||||||
method: 'POST',
|
const reply = { k: 'decision', cardId: card.id, value: picked.value, note: custom ? want.slice(7) : '试跑代答' };
|
||||||
body: JSON.stringify({ reply: { k: 'decision', cardId: card.id, value: picked.value, note: custom ? want.slice(7) : '试跑代答' } }),
|
if (Array.isArray(card.checklist) && card.checklist.length > 0 && picked.value === approveValue) reply.checked = card.checklist;
|
||||||
});
|
await api('/api/desk/chat-cards/reply', { method: 'POST', body: JSON.stringify({ reply }) });
|
||||||
answered.add(card.id);
|
answered.add(card.id);
|
||||||
asked.push(String(card.ask ?? card.title ?? ''));
|
asked.push(String(card.ask ?? card.title ?? ''));
|
||||||
count += 1;
|
count += 1;
|
||||||
console.log(` 决策卡拍板:${card.ask?.slice(0, 40) ?? ''} → ${picked.label ?? picked.value}`);
|
console.log(
|
||||||
|
` 决策卡拍板:${card.ask?.slice(0, 40) ?? ''} → ${picked.label ?? picked.value}` +
|
||||||
|
(reply.checked ? `(人工验收清单 ${reply.checked.length} 条已点全)` : ''),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
@@ -1197,6 +1226,10 @@ async function runCase(suite, kase) {
|
|||||||
let bailed = false;
|
let bailed = false;
|
||||||
let receiptChecked = false;
|
let receiptChecked = false;
|
||||||
let receiptMismatch = false;
|
let receiptMismatch = false;
|
||||||
|
/** w05「都在库·零数据卡直分析」:任意一张匹配 stages 的执行卡出现过(不论此刻答没答)就算见过 */
|
||||||
|
let dataCardSeen = false;
|
||||||
|
/** w05「类型不一致·硬拦」:`sub_eval.case.gate_reason` 命中过 `kase.expectGateReason` 那个词 */
|
||||||
|
let gateReasonMatched = false;
|
||||||
const deadline = Date.now() + 20 * 60 * 1000;
|
const deadline = Date.now() + 20 * 60 * 1000;
|
||||||
let last;
|
let last;
|
||||||
|
|
||||||
@@ -1265,6 +1298,15 @@ async function runCase(suite, kase) {
|
|||||||
(r) => String(r[where] ?? '') === equals && String(r[column] ?? '').includes(contains),
|
(r) => String(r[where] ?? '') === equals && String(r[column] ?? '').includes(contains),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// w05「都在库·零数据卡直分析」:S1 的 doneWhen 起手就成立,断言一张数据工单都没出现过
|
||||||
|
if (kase.expectNoDataCard && !dataCardSeen) {
|
||||||
|
dataCardSeen = (body.cards ?? []).some((c) => (suite.stages ?? []).some((s) => s.match.test(c.title ?? '')));
|
||||||
|
}
|
||||||
|
// w05「类型不一致·硬拦」:gate_reason 那句话要点了名(latch 住,答完卡草稿就清了,过了这一拍再查就晚了)
|
||||||
|
if (kase.expectGateReason && !gateReasonMatched) {
|
||||||
|
const caseRow = draftRows(body.drafts ?? [], 'sub_eval.case')[0];
|
||||||
|
gateReasonMatched = caseRow !== undefined && kase.expectGateReason.test(String(caseRow['gate_reason'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
// 决策卡挂在对话区,不在这一摞里 —— 每轮顺手看一眼有没有等着人点的
|
// 决策卡挂在对话区,不在这一摞里 —— 每轮顺手看一眼有没有等着人点的
|
||||||
decisions += await answerDecisions(taskId, kase, decided);
|
decisions += await answerDecisions(taskId, kase, decided);
|
||||||
@@ -1402,11 +1444,19 @@ async function runCase(suite, kase) {
|
|||||||
: ` ⚠️ 确认屏上补的联系人行没落下(或编码没续上)`,
|
: ` ⚠️ 确认屏上补的联系人行没落下(或编码没续上)`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await api('/api/desk/work-orders/reply', {
|
/*
|
||||||
method: 'POST',
|
* **人工验收清单**(同上,W05 的 T_release 终局验收卡):试跑观测面的 `cards`
|
||||||
body: JSON.stringify({ reply: { mode: 'acceptance', workOrderId: card.id, verdict: 'pass', values: {} } }),
|
* 摘要故意瘦身(`{id,mode,title,done,result}`,见 governance-ops.ts),没有
|
||||||
});
|
* `checklist` 这一格 —— 要拿清单原文得走**任务详情**的 `workOrders`(与 `card` 同一个
|
||||||
console.log(` 验收通过:${card.title}`);
|
* `id`,那边走的是完整契约 `acceptanceWorkOrderSchema`)。没有清单的卡(其余套件)
|
||||||
|
* 这次多查的一趟落空,`checked` 不进载荷,行为不变。
|
||||||
|
*/
|
||||||
|
const detail = await api(`/api/desk/tasks/${taskId}`);
|
||||||
|
const workOrder = (detail.body.workOrders ?? []).find((w) => w.id === card.id);
|
||||||
|
const acceptReply = { mode: 'acceptance', workOrderId: card.id, verdict: 'pass', values: {} };
|
||||||
|
if (Array.isArray(workOrder?.checklist) && workOrder.checklist.length > 0) acceptReply.checked = workOrder.checklist;
|
||||||
|
await api('/api/desk/work-orders/reply', { method: 'POST', body: JSON.stringify({ reply: acceptReply }) });
|
||||||
|
console.log(` 验收通过:${card.title}${acceptReply.checked ? `(人工验收清单 ${acceptReply.checked.length} 条已点全)` : ''}`);
|
||||||
replied.add(card.id);
|
replied.add(card.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1470,6 +1520,22 @@ async function runCase(suite, kase) {
|
|||||||
console.log(' ⚠️ 确认屏补录的联系人没拿到基座续的号 —— 判失败');
|
console.log(' ⚠️ 确认屏补录的联系人没拿到基座续的号 —— 判失败');
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
|
if (kase.expectNoDataCard && dataCardSeen) {
|
||||||
|
console.log(' ⚠️ 这一例本该是零数据卡直分析,却见到了一张数据工单 —— 判失败');
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (kase.expectGateReason && !gateReasonMatched) {
|
||||||
|
console.log(` ⚠️ gate_reason 没点名(期望命中 ${kase.expectGateReason})—— 判失败`);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (kase.expectAnalyzeCalls !== undefined) {
|
||||||
|
const analyzeCalls = analyzeCallCount(last?.trail ?? []);
|
||||||
|
console.log(` 模型调用 ${analyzeCalls} 次`);
|
||||||
|
if (analyzeCalls !== kase.expectAnalyzeCalls) {
|
||||||
|
console.log(` ⚠️ 模型调用该恰 ${kase.expectAnalyzeCalls} 次,实际 ${analyzeCalls} 次 —— 判失败`);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (receiptMismatch) ok = false;
|
if (receiptMismatch) ok = false;
|
||||||
if (forbidHit) {
|
if (forbidHit) {
|
||||||
console.log(` ⚠️ 计划里出现了不该有的字样(${(kase.forbidInPlan ?? []).join('/')})—— 占位人名又回来了,判失败`);
|
console.log(` ⚠️ 计划里出现了不该有的字样(${(kase.forbidInPlan ?? []).join('/')})—— 占位人名又回来了,判失败`);
|
||||||
|
|||||||
Reference in New Issue
Block a user