Archived
ACT-WORK-05:标准化参数集改子表 parts.parameter,与目标件页面结构对齐
用户核对实跑页面后指出:P-08 数据工单做出来的页面结构必须与目标件 《数据工单-01-芯片主数据创建》一致——目标件本就是两张表(表 1 主记录 / 表 2 标准化参数子表),而实现把参数集塞成了表 1 的一个文本列,违反 《SS02-SS03-SS04_Demo台账字段细化与统一方案》§六「重复明细用子表承载, 不在单一文本字段堆叠」。 五处联动改齐: - 平台基线新增 parts.parameter 主数据表(内部料号+参数名复合主键), 两颗样例料的参数集从 normalized_parameter_set 文本列拆成 7 行子表数据 (数值原样搬,一个字未改);parts.profile 物理列保留、不再赋值。 - 活动清单:parts.profile 撤掉该字段声明,新增 parts.parameter 声明 (internal_pn 引用墙、param_value/constraint_type 必填);版本号 1.1.1 → 1.2.0。 - 案例模型:S0d_p08_master/S1_input_confirm/S2_normalize 的读写面与 guide 全部改指向新子表;顺带修正候选计数措辞歧义(实测样例0一张该发 的决策卡被跳过——"候选absent且候选数≥2"被模型读成"不在库的候选≥2颗", 改成先分别数"候选总数"与"不在库的候选数"两个数、显式说清楚判据认哪个)。 - caseFileModel 补 parts_parameter_missing 读数,两条哨兵(s_p08_created / s_pns_resolved)一并要求这一列不缺必填。 - 报告模版补齐表 2(逐块搬自目标件),主键列挂 data-col,约束类型走 data-class-of 徽标。 - e2e 套件加性支持"一张卡填两张表"(stage.extraTables),buildEdits 的 rowKey 泛化支持复合主键数组形态(不破坏既有八套件)。 验证:vitest 413 passed(活动包+平台种子测试全绿)、typecheck/lint/ depcruise 三绿、activities:install 零静态审发现、SQL 核对 parts.parameter 主键与行数(两颗料各7行)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
+111
-52
@@ -34,6 +34,22 @@ const E2E_CLIENT_SECRET = process.env.KEYCLOAK_E2E_CLIENT_SECRET ?? '';
|
||||
/** access token 只活 5 分钟:超过 4 分钟就换一枚,免得长案例中途 401 */
|
||||
const TOKEN_REFRESH_MS = 4 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* 标准化参数子表 `parts.parameter` 的行(w05 专用)—— 按 [参数名, 值, 单位, 约束, 说明]
|
||||
* 五元组批量转成保存载荷的行对象;数值照抄各案例原先写在 `normalized_parameter_set`
|
||||
* 那个文本列里的那几行,一个数都不改(2026-08-27 结构改:参数集拆成独立子表)。
|
||||
*/
|
||||
function paramRows(internalPn, rows) {
|
||||
return rows.map(([param_name, param_value, param_unit, constraint_type, param_note]) => ({
|
||||
internal_pn: internalPn,
|
||||
param_name,
|
||||
param_value,
|
||||
param_unit,
|
||||
constraint_type,
|
||||
param_note,
|
||||
}));
|
||||
}
|
||||
|
||||
/** 每套活动的案例:样例序号 + 人在件上要填的格子(按 pn 对行) */
|
||||
const SUITES = {
|
||||
a1: {
|
||||
@@ -519,6 +535,8 @@ const SUITES = {
|
||||
blankCol: 'supplement',
|
||||
stages: [
|
||||
{
|
||||
// 这张卡摆两张表(表 1 主记录 + 表 2 标准化参数子表)—— 主表照旧,子表挂在 extraTables 里
|
||||
// 加性表达(见 buildEdits 顶注),保存时两张表的行一次并进同一个 `tables` 载荷。
|
||||
match: /数据工单 · 芯片主数据创建/,
|
||||
address: 'parts.profile',
|
||||
rowKey: 'internal_pn',
|
||||
@@ -531,8 +549,19 @@ const SUITES = {
|
||||
specification_description: '兜底规格(案例没给参数集)',
|
||||
package: 'TO-263-5',
|
||||
lifecycle_status: 'active',
|
||||
normalized_parameter_set: '输入电压范围 | 4.5~40 | V | hard | 兜底',
|
||||
},
|
||||
extraTables: [
|
||||
{
|
||||
// 主键是 (internal_pn, param_name) 复合列 —— 两颗料同名参数(如都有「工作温度」)
|
||||
// 不这样区分,rows.find 会永远命中第一条同名行、把不同料的值互相顶掉。
|
||||
address: 'parts.parameter',
|
||||
rowKey: ['internal_pn', 'param_name'],
|
||||
blankCol: 'param_value',
|
||||
edits: [],
|
||||
// 案例没点名的参数行兜底(模型骨架只给了 internal_pn+param_name、案例没接住时才会用到)
|
||||
fillAllBlank: { param_value: '未披露', constraint_type: 'hard', param_note: '兜底(案例没给这一条)' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
match: /数据工单 · 原厂技术资料创建/,
|
||||
@@ -586,17 +615,17 @@ const SUITES = {
|
||||
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.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', '原厂未给'],
|
||||
]),
|
||||
'parts.document': [
|
||||
{
|
||||
internal_pn: 'XL2596S-5.0',
|
||||
@@ -638,17 +667,17 @@ const SUITES = {
|
||||
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.parameter': paramRows('LM2596S-3.3', [
|
||||
['输入电压范围', '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 阈值'],
|
||||
]),
|
||||
'parts.document': [
|
||||
{
|
||||
internal_pn: 'LM2596S-3.3',
|
||||
@@ -678,15 +707,6 @@ const SUITES = {
|
||||
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',
|
||||
@@ -694,17 +714,28 @@ const SUITES = {
|
||||
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.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' },
|
||||
@@ -888,38 +919,53 @@ async function answerEntries(taskId, kase, answered) {
|
||||
* 把案例声明的格子对齐成保存载荷。行键随套件走(`rowKey`,老套件默认 `pn`):
|
||||
* 键为空串 = 按序发给待填行;`fillMissing` 单列兜底扫空;
|
||||
* `fillAllBlank` 整行兜底扫空(回执这类「几行由模型定、每行要填一把」的表用它)。
|
||||
*
|
||||
* **复合主键**(2026-08-27,w05 的 `parts.parameter` 主键是 `(internal_pn, param_name)`)——
|
||||
* `rowKey` 除了旧的单列字符串形态,也接受一个列名数组:两颗料同名参数(如都有「工作温度」)
|
||||
* 若只按 `param_name` 单列匹配,`rows.find` 会永远命中第一条同名行、把不同料的值互相顶掉;
|
||||
* 数组形态按**全部列同时相等**才算命中同一行,单列字符串形态原样保留旧行为(不影响既有八套件)。
|
||||
*/
|
||||
function buildEdits(stage, drafts, kase = {}) {
|
||||
const rows = draftRows(drafts, stage.address);
|
||||
const rowKey = stage.rowKey ?? 'pn';
|
||||
const keyCols = Array.isArray(stage.rowKey) ? stage.rowKey : [stage.rowKey ?? 'pn'];
|
||||
const blankCol = stage.blankCol ?? 'base_price';
|
||||
const pending = rows.filter((r) => String(r[blankCol] ?? '') === '');
|
||||
const lines = [];
|
||||
const taken = new Set();
|
||||
const sig = (obj) => keyCols.map((k) => String(obj[k] ?? '')).join('');
|
||||
const keyOf = (obj) => Object.fromEntries(keyCols.map((k) => [k, String(obj[k] ?? '')]));
|
||||
// 案例级按表的编辑叠在套件级之上(同一套件不同案例要建的料号不同 —— 参数集不能是同一份)
|
||||
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];
|
||||
const cells = { ...edit };
|
||||
const keyVals = {};
|
||||
for (const k of keyCols) {
|
||||
keyVals[k] = cells[k];
|
||||
delete cells[k];
|
||||
}
|
||||
const allGiven = keyCols.every((k) => keyVals[k] !== undefined && keyVals[k] !== '');
|
||||
const target = allGiven ? rows.find((r) => keyCols.every((k) => String(r[k]) === String(keyVals[k]))) : pending[i];
|
||||
if (!target) {
|
||||
console.log(` ⚠️ 找不到要填的行(${rowKey}=${keyed || `待填第${i}行`});现有行:${rows.map((r) => r[rowKey]).join(', ')}`);
|
||||
const wanted = allGiven ? keyCols.map((k) => keyVals[k]).join('/') : `待填第${i}行`;
|
||||
const have = rows.map((r) => keyCols.map((k) => r[k]).join('/')).join(', ');
|
||||
console.log(` ⚠️ 找不到要填的行(${keyCols.join('+')}=${wanted});现有行:${have}`);
|
||||
continue;
|
||||
}
|
||||
taken.add(String(target[rowKey]));
|
||||
lines.push({ [rowKey]: String(target[rowKey]), ...cells });
|
||||
taken.add(sig(target));
|
||||
lines.push({ ...keyOf(target), ...cells });
|
||||
}
|
||||
if (stage.fillMissing) {
|
||||
const { column, value } = stage.fillMissing;
|
||||
for (const r of rows) {
|
||||
if (String(r[column] ?? '') === '' && !taken.has(String(r[rowKey]))) {
|
||||
lines.push({ [rowKey]: String(r[rowKey]), [column]: value });
|
||||
if (String(r[column] ?? '') === '' && !taken.has(sig(r))) {
|
||||
lines.push({ ...keyOf(r), [column]: value });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stage.fillAllBlank) {
|
||||
for (const r of rows) {
|
||||
if (String(r[blankCol] ?? '') === '' && !taken.has(String(r[rowKey]))) {
|
||||
lines.push({ [rowKey]: String(r[rowKey]), ...stage.fillAllBlank });
|
||||
if (String(r[blankCol] ?? '') === '' && !taken.has(sig(r))) {
|
||||
lines.push({ ...keyOf(r), ...stage.fillAllBlank });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -930,8 +976,8 @@ function buildEdits(stage, drafts, kase = {}) {
|
||||
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 });
|
||||
if (String(r[column] ?? '') === equals && !taken.has(sig(r))) {
|
||||
lines.push({ ...keyOf(r), ...values });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1195,12 +1241,25 @@ async function runCase(suite, kase) {
|
||||
addedRows = true;
|
||||
console.log(` 件上加 ${kase.adds.rows.length} 行(${kase.adds.rows.map((r) => Object.values(r)[0]).join('、')})`);
|
||||
}
|
||||
if (lines.length > 0) {
|
||||
/*
|
||||
* **一张卡填两张表**(w05:P-08 数据工单同一张卡上摆着表 1 主记录 + 表 2 标准化参数子表)——
|
||||
* 加性写法:`stage.extraTables`(可选)是同形状的段描述数组,各自按自己的 address 走
|
||||
* 同一套 `buildEdits`;保存载荷 `{tables:{地址:行[]}}` 本就支持多地址一次发,不必分两次保存、
|
||||
* 也不影响没有 `extraTables` 的老套件(a1/a2/a3/a4/a6/a7/e04/i01 的 stage 都没有这个键)。
|
||||
*/
|
||||
const tables = {};
|
||||
if (lines.length > 0) tables[stage.address] = lines;
|
||||
for (const extra of stage.extraTables ?? []) {
|
||||
const extraLines = buildEdits(extra, body.drafts ?? [], kase);
|
||||
if (extraLines.length > 0) tables[extra.address] = extraLines;
|
||||
}
|
||||
const tableAddresses = Object.keys(tables);
|
||||
if (tableAddresses.length > 0) {
|
||||
await api(`/api/desk/tasks/${taskId}/table`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ tables: { [stage.address]: lines } }),
|
||||
body: JSON.stringify({ tables }),
|
||||
});
|
||||
console.log(` 件上保存 ${lines.length} 行(${card.title})`);
|
||||
console.log(` 件上保存 ${tableAddresses.map((a) => `${a}:${tables[a].length}行`).join(' + ')}(${card.title})`);
|
||||
}
|
||||
try {
|
||||
await submitExecution(card.id);
|
||||
|
||||
Reference in New Issue
Block a user