Archived
test(w05): e2e 套件 —— 五案例(候选缺库 / 常路 / 原 PN 硬阻塞 / 三候选含不推荐 / 新客户建档)
套件代码就位(SUITES.w05 + buildEdits/expectGateBlocks 加性扩展 stageEdits/fillWhere + main() 套件名单加入 w05);node --check 与 pnpm lint 全绿,五案例结构(sample 0..4、三条 stage 正则、decideSeq/decide 取值集合、stageEdits 地址与 stages 地址对齐)已用一次性脚本 静态核对。缺 KEYCLOAK_E2E_CLIENT_SECRET,套件本身待凭证到位后跑。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
+235
-5
@@ -9,6 +9,7 @@
|
|||||||
* node tools/e2e-a2.mjs a1 # 只跑 A1 回归(执行卡零输入 + 提交闸回落)
|
* node tools/e2e-a2.mjs a1 # 只跑 A1 回归(执行卡零输入 + 提交闸回落)
|
||||||
* node tools/e2e-a2.mjs e04 # 只跑 E04 三案例(两张执行单 + 决策卡审批循环)
|
* node tools/e2e-a2.mjs e04 # 只跑 E04 三案例(两张执行单 + 决策卡审批循环)
|
||||||
* node tools/e2e-a2.mjs i01 # 只跑 I01 三案例(一张执行单 + 初稿审核决策卡 + 发布放行)
|
* 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 # 全跑
|
||||||
* node tools/e2e-a2.mjs a2 1 # 只跑 A2 的第 1 条(0 起)
|
* 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 = '';
|
let bearer = '';
|
||||||
@@ -608,7 +822,9 @@ async function expectGateBlocks(cardId, taskId, stage) {
|
|||||||
if (column === undefined) return 'nothing-missing';
|
if (column === undefined) return 'nothing-missing';
|
||||||
const { body } = await api(`/api/gov/testbed/activity/${taskId}`);
|
const { body } = await api(`/api/gov/testbed/activity/${taskId}`);
|
||||||
const rows = draftRows(body.drafts ?? [], stage.address);
|
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';
|
return blank > 0 ? 'leaked' : 'nothing-missing';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,14 +889,16 @@ async function answerEntries(taskId, kase, answered) {
|
|||||||
* 键为空串 = 按序发给待填行;`fillMissing` 单列兜底扫空;
|
* 键为空串 = 按序发给待填行;`fillMissing` 单列兜底扫空;
|
||||||
* `fillAllBlank` 整行兜底扫空(回执这类「几行由模型定、每行要填一把」的表用它)。
|
* `fillAllBlank` 整行兜底扫空(回执这类「几行由模型定、每行要填一把」的表用它)。
|
||||||
*/
|
*/
|
||||||
function buildEdits(stage, drafts) {
|
function buildEdits(stage, drafts, kase = {}) {
|
||||||
const rows = draftRows(drafts, stage.address);
|
const rows = draftRows(drafts, stage.address);
|
||||||
const rowKey = stage.rowKey ?? 'pn';
|
const rowKey = stage.rowKey ?? 'pn';
|
||||||
const blankCol = stage.blankCol ?? 'base_price';
|
const blankCol = stage.blankCol ?? 'base_price';
|
||||||
const pending = rows.filter((r) => String(r[blankCol] ?? '') === '');
|
const pending = rows.filter((r) => String(r[blankCol] ?? '') === '');
|
||||||
const lines = [];
|
const lines = [];
|
||||||
const taken = new Set();
|
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 { [rowKey]: keyed, ...cells } = edit;
|
||||||
const target = keyed !== '' ? rows.find((r) => String(r[rowKey]) === keyed) : pending[i];
|
const target = keyed !== '' ? rows.find((r) => String(r[rowKey]) === keyed) : pending[i];
|
||||||
if (!target) {
|
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;
|
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) {
|
if (kase.adds && kase.adds.address === stage.address && !addedRows) {
|
||||||
lines.push(...kase.adds.rows);
|
lines.push(...kase.adds.rows);
|
||||||
@@ -1098,7 +1328,7 @@ async function main() {
|
|||||||
const which = process.argv[2] ?? 'all';
|
const which = process.argv[2] ?? 'all';
|
||||||
const only = process.argv[3] === undefined ? undefined : Number(process.argv[3]);
|
const only = process.argv[3] === undefined ? undefined : Number(process.argv[3]);
|
||||||
await login();
|
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 pass = 0;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
for (const key of suites) {
|
for (const key of suites) {
|
||||||
|
|||||||
Reference in New Issue
Block a user