diff --git a/tools/e2e-a2.mjs b/tools/e2e-a2.mjs index 1364080..e609ca2 100644 --- a/tools/e2e-a2.mjs +++ b/tools/e2e-a2.mjs @@ -9,6 +9,7 @@ * node tools/e2e-a2.mjs a1 # 只跑 A1 回归(执行卡零输入 + 提交闸回落) * node tools/e2e-a2.mjs e04 # 只跑 E04 三案例(两张执行单 + 决策卡审批循环) * node tools/e2e-a2.mjs i01 # 只跑 I01 三案例(一张执行单 + 初稿审核决策卡 + 发布放行) + * node tools/e2e-a2.mjs w05 # 只跑 W05 五案例(ACT-WORK-05 差异评估:条件数据工单 P-08/P-10 + 执行单 + 决策卡审核循环 + 放行) * node tools/e2e-a2.mjs # 全跑 * node tools/e2e-a2.mjs a2 1 # 只跑 A2 的第 1 条(0 起) * @@ -507,6 +508,219 @@ const SUITES = { }, ], }, + /* + * ACT-WORK-05 差异评估 —— 1 数据(条件,P-08 / P-10 各一张)+ 1 执行 + 2 验收(审核走决策卡循环、放行终局)。 + * 库里只播了 LM2596S-5.0 / LM2596S-ADJ:XL2596S-5.0、CN-DCDC-32V、LM2596S-3.3 刻意不在, + * 「候选缺库 / 原 PN 缺库 / 硬约束不推荐」三条路靠它们。e2e 关落真库,主数据不落正式表,案例可反复跑。 + */ + w05: { + activityId: 'part-substitution-assessment', + address: 'sub_eval.input_gap', + blankCol: 'supplement', + stages: [ + { + match: /数据工单 · 芯片主数据创建/, + address: 'parts.profile', + rowKey: 'internal_pn', + blankCol: 'brand_name', + expectGateBlock: true, + edits: [], + // 案例没点名的料号兜底(不该走到这里 —— 每个案例都在 stageEdits 里逐颗给了) + fillAllBlank: { + brand_name: '兜底品牌', + specification_description: '兜底规格(案例没给参数集)', + package: 'TO-263-5', + lifecycle_status: 'active', + normalized_parameter_set: '输入电压范围 | 4.5~40 | V | hard | 兜底', + }, + }, + { + match: /数据工单 · 原厂技术资料创建/, + address: 'parts.document', + rowKey: 'internal_pn', + blankCol: 'document_title', + expectGateBlock: 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: [ + { + sample: 0, + name: '候选缺库 · XL2596S-5.0 派数据工单(P-08 + P-10)· 审核先点名驳回再通过', + // 决策卡序:候选 ≥2 缺库先问 → create;审核第一张点名意见驳回;第二张通过 + decideSeq: ['create', 'custom:使能阈值那条 UNKNOWN 的去向请写明验证任务号与测试条件', 'approve'], + expectDecision: true, + expectPort: 'assessment-released', + stageEdits: { + 'parts.profile': [ + { + internal_pn: 'XL2596S-5.0', + brand_name: 'XLSEMI', + manufacturer_name: 'XLSEMI', + specification_description: '降压型 DCDC 稳压器,3A,固定 5.0V,150kHz', + package: 'TO-263-5', + process_technology: 'BCD', + lifecycle_status: 'active', + datasheet_ref: 'https://www.xlsemi.com/datasheet/XL2596S.pdf', + datasheet_version: 'Rev C', + application_scenarios: '工业电源;消费电源', + normalized_parameter_set: [ + '输入电压范围 | 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 | 原厂未给', + ].join('\n'), + }, + ], + '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 → 条件推荐(工具算的,不是模型说的) + expectRevised: { address: 'substitution.relation', where: 'candidate_internal_pn', equals: 'XL2596S-5.0', column: 'recommendation_level', contains: 'conditional' }, + }, + { + sample: 1, + name: '都在库 · 常路 · 可调版 → 备选 · 审核一次通过', + decide: 'approve', + expectPort: 'assessment-released', + expectRevised: { address: 'substitution.relation', where: 'candidate_internal_pn', equals: 'LM2596S-ADJ', column: 'recommendation_level', contains: 'alternate' }, + }, + { + sample: 2, + name: '原 PN 缺库 · 硬阻塞建档(不问人)· 审核通过', + decide: 'approve', + expectPort: 'assessment-released', + stageEdits: { + 'parts.profile': [ + { + internal_pn: 'LM2596S-3.3', + brand_name: 'TI', + manufacturer_name: 'Texas Instruments', + specification_description: '降压型 DCDC 稳压器,3A,固定 3.3V,150kHz', + package: 'TO-263-5', + process_technology: 'BCD', + lifecycle_status: 'nrnd', + datasheet_ref: 'https://www.ti.com/lit/ds/symlink/lm2596.pdf', + datasheet_version: 'Rev H (2023-09)', + normalized_parameter_set: [ + '输入电压范围 | 4.5~40 | V | hard | 客户上限 40V', + '输出电压 | 3.3 | V | hard | 固定版', + '输出电流 | 3 | A | hard | 满载能力', + '开关频率 | 150 | kHz | hard | 影响外围', + '静态电流 | 5 | mA | soft | 待机功耗', + '工作温度 | -40~+85 | ℃ | hard | 工业温域', + '使能阈值 | 1.4 | V | hard | ON/OFF 阈值', + ].join('\n'), + }, + ], + '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: ['create', 'create', 'approve'], + expectDecision: true, + 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', + normalized_parameter_set: [ + '输入电压范围 | 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 | 原厂未给', + ].join('\n'), + }, + { + internal_pn: 'CN-DCDC-32V', + brand_name: '国产某厂', + specification_description: '降压型 DCDC,3A,固定 5.0V,输入上限 32V', + package: 'TO-263-5', + lifecycle_status: 'active', + normalized_parameter_set: [ + '输入电压范围 | 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 阈值', + ].join('\n'), + }, + ], + '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, + name: '新客户 · 录入卡先建档 · 审核通过', + entryValues: { name: '深圳恒星电源有限公司', industry: '工业电源', region: '广东' }, + decide: 'approve', + expectPort: 'assessment-released', + }, + ], + }, }; let bearer = ''; @@ -608,7 +822,9 @@ async function expectGateBlocks(cardId, taskId, stage) { if (column === undefined) return 'nothing-missing'; const { body } = await api(`/api/gov/testbed/activity/${taskId}`); const rows = draftRows(body.drafts ?? [], stage.address); - const blank = rows.filter((r) => String(r[column] ?? '').trim() === '').length; + // 有 fillWhere 的段只数该档的行:default / ok 行空着是本分,不是「闸没拦」 + const scoped = stage.fillWhere ? rows.filter((r) => String(r[stage.fillWhere.column] ?? '') === stage.fillWhere.equals) : rows; + const blank = scoped.filter((r) => String(r[column] ?? '').trim() === '').length; return blank > 0 ? 'leaked' : 'nothing-missing'; } @@ -673,14 +889,16 @@ async function answerEntries(taskId, kase, answered) { * 键为空串 = 按序发给待填行;`fillMissing` 单列兜底扫空; * `fillAllBlank` 整行兜底扫空(回执这类「几行由模型定、每行要填一把」的表用它)。 */ -function buildEdits(stage, drafts) { +function buildEdits(stage, drafts, kase = {}) { const rows = draftRows(drafts, stage.address); const rowKey = stage.rowKey ?? 'pn'; const blankCol = stage.blankCol ?? 'base_price'; const pending = rows.filter((r) => String(r[blankCol] ?? '') === ''); const lines = []; const taken = new Set(); - for (const [i, edit] of (stage.edits ?? []).entries()) { + // 案例级按表的编辑叠在套件级之上(同一套件不同案例要建的料号不同 —— 参数集不能是同一份) + const edits = [...(stage.edits ?? []), ...((kase.stageEdits ?? {})[stage.address] ?? [])]; + for (const [i, edit] of edits.entries()) { const { [rowKey]: keyed, ...cells } = edit; const target = keyed !== '' ? rows.find((r) => String(r[rowKey]) === keyed) : pending[i]; if (!target) { @@ -705,6 +923,18 @@ function buildEdits(stage, drafts) { } } } + /* + * 按条件整行扫空:只给某一档状态的行填(评估输入清单里只有 status = gap 的行要补充内容, + * default / ok 的行本就不该填 —— fillAllBlank 会把它们也填上,那是替人说了他没说的话)。 + */ + if (stage.fillWhere) { + const { column, equals, values } = stage.fillWhere; + for (const r of rows) { + if (String(r[column] ?? '') === equals && !taken.has(String(r[rowKey]))) { + lines.push({ [rowKey]: String(r[rowKey]), ...values }); + } + } + } return lines; } @@ -958,7 +1188,7 @@ async function runCase(suite, kase) { : ` ⚠️ 缺着必填还放过去了:提交闸失效`, ); } - const lines = buildEdits(stage, body.drafts ?? []); + const lines = buildEdits(stage, body.drafts ?? [], kase); // 人加行:与真人在件上「加一行」同一条链 —— 新主键的行落进保存载荷即是新增 if (kase.adds && kase.adds.address === stage.address && !addedRows) { lines.push(...kase.adds.rows); @@ -1098,7 +1328,7 @@ async function main() { const which = process.argv[2] ?? 'all'; const only = process.argv[3] === undefined ? undefined : Number(process.argv[3]); await login(); - const suites = which === 'all' || which === 'both' ? ['a1', 'a2', 'a3', 'a4', 'a6', 'a7', 'e04', 'i01'] : [which]; + const suites = which === 'all' || which === 'both' ? ['a1', 'a2', 'a3', 'a4', 'a6', 'a7', 'e04', 'i01', 'w05'] : [which]; let pass = 0; let total = 0; for (const key of suites) {