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:
@@ -76,11 +76,17 @@ describe('清单本身合规', () => {
|
||||
});
|
||||
|
||||
describe('台账声明:授权面与《统一方案》口径', () => {
|
||||
it('P-08 一张表:主键 internal_pn,参数集是它的一个必填字段,没有 parts.parameter 子表', () => {
|
||||
it('P-08 主键 internal_pn,标准化参数集拆成独立子表 parts.parameter(2026-08-27 结构改:与目标件一致)', () => {
|
||||
const p08 = byAddress('parts', 'profile');
|
||||
expect(p08?.fields.filter((f) => f.primaryKey).map((f) => f.name)).toEqual(['internal_pn']);
|
||||
expect(p08?.fields.find((f) => f.name === 'normalized_parameter_set')?.required).toBe(true);
|
||||
expect(byAddress('parts', 'parameter')).toBeUndefined();
|
||||
expect(p08?.fields.find((f) => f.name === 'normalized_parameter_set')).toBeUndefined();
|
||||
const param = byAddress('parts', 'parameter');
|
||||
expect(param?.kind).toBe('master');
|
||||
expect(param?.fields.filter((f) => f.primaryKey).map((f) => f.name)).toEqual(['internal_pn', 'param_name']);
|
||||
expect(param?.fields.find((f) => f.name === 'internal_pn')?.refersTo).toBe('parts.profile.internal_pn');
|
||||
expect(param?.fields.find((f) => f.name === 'param_value')?.required).toBe(true);
|
||||
expect(param?.fields.find((f) => f.name === 'constraint_type')?.tally).toEqual(['hard', 'soft']);
|
||||
expect(param?.fields.find((f) => f.name === 'constraint_type')?.required).toBe(true);
|
||||
});
|
||||
|
||||
it('P-11 主记录的原 PN / 候选 PN 都是引用墙(查无此料当场拒 —— 存在性检查的硬门)', () => {
|
||||
@@ -186,12 +192,16 @@ describe('案例模型与清单的对齐', () => {
|
||||
});
|
||||
|
||||
describe('报告模版:编辑面与只读面各就各位', () => {
|
||||
it('两张数据工单件可加行可编辑:data-edit / data-edit-add 与主键 data-col 都在', () => {
|
||||
it('P-08 数据工单件的表 1 + 表 2(标准化参数子表)都可加行可编辑:data-edit / data-edit-add 与主键 data-col 都在', () => {
|
||||
const p08 = viewHtml('p08-master-create');
|
||||
expect(p08).toContain('data-edit="parts.profile"');
|
||||
expect(p08).toContain('data-edit-add="parts.profile"');
|
||||
expect(p08).toContain('data-col="internal_pn"');
|
||||
expect(p08).toContain('data-col="normalized_parameter_set"');
|
||||
expect(p08).toContain('data-edit="parts.parameter"');
|
||||
expect(p08).toContain('data-edit-add="parts.parameter"');
|
||||
expect(p08).toContain('data-col="param_name"');
|
||||
expect(p08).toContain('data-col="param_value"');
|
||||
expect(p08).toContain('data-col="constraint_type"');
|
||||
const p10 = viewHtml('p10-document-create');
|
||||
expect(p10).toContain('data-edit="parts.document"');
|
||||
expect(p10).toContain('data-col="doc_seq"');
|
||||
@@ -211,15 +221,15 @@ describe('报告模版:编辑面与只读面各就各位', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('每个可编辑格子的 data-col 都在对应台账对象的声明里', () => {
|
||||
const cases: Array<[string, string, string]> = [
|
||||
['p08-master-create', 'parts', 'profile'],
|
||||
['p10-document-create', 'parts', 'document'],
|
||||
['eval-input-list', D, 'input_gap'],
|
||||
it('每个可编辑格子的 data-col 都在对应台账对象的声明里(p08-master-create 一屏两张表,两张台账对象的声明并起来看)', () => {
|
||||
const cases: Array<[string, Array<[string, string]>]> = [
|
||||
['p08-master-create', [['parts', 'profile'], ['parts', 'parameter']]],
|
||||
['p10-document-create', [['parts', 'document']]],
|
||||
['eval-input-list', [[D, 'input_gap']]],
|
||||
];
|
||||
for (const [view, domain, object] of cases) {
|
||||
for (const [view, objects] of cases) {
|
||||
const cols = new Set([...viewHtml(view).matchAll(/data-col="([^"]+)"/g)].map((m) => m[1] ?? ''));
|
||||
const declared = new Set(byAddress(domain, object)?.fields.map((f) => f.name));
|
||||
const declared = new Set(objects.flatMap(([domain, object]) => byAddress(domain, object)?.fields.map((f) => f.name) ?? []));
|
||||
for (const c of cols) expect(declared.has(c), `${view}: ${c}`).toBe(true);
|
||||
}
|
||||
});
|
||||
@@ -268,6 +278,7 @@ describe('徽标别名与值域相关的坑(2026-08-27 实测账,回归网)', ()
|
||||
/** 每张模版真的摆了徽标的那几列(带下划线的 tally 值最容易漏,漏了就是「待定」那次事故) */
|
||||
const BADGED: ReadonlyArray<{ view: string; domain: string; object: string; column: string }> = [
|
||||
{ view: 'p08-master-create', domain: 'parts', object: 'profile', column: 'lifecycle_status' },
|
||||
{ view: 'p08-master-create', domain: 'parts', object: 'parameter', column: 'constraint_type' },
|
||||
{ view: 'p10-document-create', domain: 'parts', object: 'document', column: 'document_type' },
|
||||
{ view: 'p10-document-create', domain: 'parts', object: 'document', column: 'document_status' },
|
||||
{ view: 'eval-input-list', domain: D, object: 'pn_check', column: 'exists_status' },
|
||||
@@ -319,6 +330,7 @@ describe('可编辑件的主键与必填列(2026-08-27 复审:主键丢钥匙 /
|
||||
/** 三张真能编辑的件与它们各自绑定的台账对象 */
|
||||
const EDITABLE: ReadonlyArray<{ view: string; domain: string; object: string }> = [
|
||||
{ view: 'p08-master-create', domain: 'parts', object: 'profile' },
|
||||
{ view: 'p08-master-create', domain: 'parts', object: 'parameter' },
|
||||
{ view: 'p10-document-create', domain: 'parts', object: 'document' },
|
||||
{ view: 'eval-input-list', domain: D, object: 'input_gap' },
|
||||
];
|
||||
|
||||
@@ -154,13 +154,22 @@ describe('说明书与剧本的骨架', () => {
|
||||
expect(s3).toContain('工具调用后同拍其余动作作废');
|
||||
});
|
||||
|
||||
it('S0 剧本写明都在库时数据工单两项本就不会进「当下可为」;S0d_p08_master 剧本写明候选 ≥2 才问、=1 必建、原 PN 硬阻塞', () => {
|
||||
it('S0 剧本写明都在库时数据工单两项本就不会进「当下可为」;S0d_p08_master 剧本按候选总数判断该不该问人(=1 必建、≥2 才问、原 PN 硬阻塞)', () => {
|
||||
const s0 = ASSESSMENT_STEPS.find((s) => s.item === 'S0_bind')?.guide ?? '';
|
||||
expect(s0).toContain('当下可为');
|
||||
expect(s0).toContain('不要对计划项用');
|
||||
const s0d = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master')?.guide ?? '';
|
||||
expect(s0d).toContain('硬阻塞');
|
||||
expect(s0d).toContain('候选只有 1 颗');
|
||||
expect(s0d).toContain('候选总数 = 1');
|
||||
});
|
||||
|
||||
it('S0d_p08_master 剧本按候选总数(不是「不在库的颗数」)判断要不要问人 —— 候选总数 ≥2 时逐颗不在库的都要问(2026-08-27 实测逼出的措辞修正)', () => {
|
||||
const s0d = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master')?.guide ?? '';
|
||||
expect(s0d).toContain('候选总数');
|
||||
expect(s0d).toContain('不在库');
|
||||
expect(s0d).toMatch(/候选总数\s*≥\s*2/);
|
||||
// 反面提示:「不在库的只有一颗」不该被读成跳过决策卡的理由
|
||||
expect(s0d).toContain('不是跳过决策卡的理由');
|
||||
});
|
||||
|
||||
it('s_bound 哨兵真的检查了「有料号不在库」这件事(都在库时门不该开;退回旧写法会红)', () => {
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
* view 恒压过模型当拍报的 say.view,一个计划项只能绑一份画面,P-08 与 P-10 各自要一份可编辑的
|
||||
* 工作区件,不能共用同一个 view):
|
||||
* 启动期 → S0_bind(AI:铸案号、建 P-01 V1、锁规则版本、逐个 PN 查库、写存在性核对表)
|
||||
* 0.5a 等待数据(建主数据)→ S0d_p08_master(人:数据工单 P-08;候选 ≥2 缺库先决策卡;
|
||||
* 0.5a 等待数据(建主数据)→ S0d_p08_master(人:数据工单 P-08,件上表 1 主记录 + 表 2 标准化
|
||||
* 参数子表 `parts.parameter`;候选总数 ≥2 时缺库先决策卡;
|
||||
* 都在库时哨兵不开,这一项与下一项都不会进「当下可为」)
|
||||
* 0.5b 等待数据(建资料)→ S0d_p10_document(人:数据工单 P-10;都在库时同样不会进「当下可为」)
|
||||
* 1 建立评估上下文 → S1_input_confirm(人:执行工单 01,评估输入清单,件上补缺口)
|
||||
@@ -41,6 +42,7 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<caseFileItem id="sub_eval_verdict_rows" name="结论行数(基座现数)"/>
|
||||
<caseFileItem id="parts_profile_rows" name="本单建的芯片主数据行数(基座现数)"/>
|
||||
<caseFileItem id="parts_profile_missing" name="本单建的芯片主数据缺必填的行数(基座现数)"/>
|
||||
<caseFileItem id="parts_parameter_missing" name="本单建的标准化参数集缺必填的行数(基座现数)"/>
|
||||
<caseFileItem id="parts_document_missing" name="本单建的资料记录缺必填的行数(基座现数)"/>
|
||||
<caseFileItem id="input_confirmed_total" name="评估输入已确认(人环凭据)"/>
|
||||
<caseFileItem id="sub_eval_input_gap_rows" name="评估输入清单行数(基座现数)"/>
|
||||
@@ -66,18 +68,18 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<onPart id="op_b" caseFileItemRef="sub_eval_pn_check_exists_status_absent"><standardEvent>update</standardEvent></onPart>
|
||||
<ifPart id="if_b"><condition>demand_request_rows > 0 and sub_eval_pn_check_rows > 0 and sub_eval_pn_check_exists_status_absent > 0</condition></ifPart>
|
||||
</sentry>
|
||||
<!-- ②′ 芯片主数据已建齐 —— 补资料那一步的入场前提(基座现数) -->
|
||||
<!-- ②′ 芯片主数据已建齐(表 1 主记录 + 表 2 参数子表都不缺必填)—— 补资料那一步的入场前提(基座现数) -->
|
||||
<sentry id="s_p08_created">
|
||||
<onPart id="op_p8" caseFileItemRef="parts_profile_rows"><standardEvent>update</standardEvent></onPart>
|
||||
<ifPart id="if_p8"><condition>parts_profile_rows > 0 and parts_profile_missing = 0</condition></ifPart>
|
||||
<ifPart id="if_p8"><condition>parts_profile_rows > 0 and parts_profile_missing = 0 and parts_parameter_missing = 0</condition></ifPart>
|
||||
</sentry>
|
||||
<!--
|
||||
② 料号全部落定:原 PN + 每个候选各一行核对,没有一颗还「不在库」,放弃的少于候选数,
|
||||
本单建的主数据 / 资料不缺必填 —— 评估输入确认那一步的入场前提。全是基座现数。
|
||||
本单建的主数据 / 参数子表 / 资料不缺必填 —— 评估输入确认那一步的入场前提。全是基座现数。
|
||||
-->
|
||||
<sentry id="s_pns_resolved">
|
||||
<onPart id="op_pr" caseFileItemRef="sub_eval_pn_check_exists_status_absent"><standardEvent>update</standardEvent></onPart>
|
||||
<ifPart id="if_pr"><condition>sub_eval_pn_check_rows = count(candidateInternalPns) + 1 and sub_eval_pn_check_missing = 0 and sub_eval_pn_check_exists_status_absent = 0 and sub_eval_pn_check_exists_status_abandoned < count(candidateInternalPns) and parts_profile_missing = 0 and parts_document_missing = 0</condition></ifPart>
|
||||
<ifPart id="if_pr"><condition>sub_eval_pn_check_rows = count(candidateInternalPns) + 1 and sub_eval_pn_check_missing = 0 and sub_eval_pn_check_exists_status_absent = 0 and sub_eval_pn_check_exists_status_abandoned < count(candidateInternalPns) and parts_profile_missing = 0 and parts_parameter_missing = 0 and parts_document_missing = 0</condition></ifPart>
|
||||
</sentry>
|
||||
<!-- ③ 评估输入已确认(旗 + 清单有行且必填齐)—— 标准化那一步的入场前提 -->
|
||||
<sentry id="s_input_confirmed">
|
||||
@@ -179,8 +181,9 @@ export const NARRATIVE = `§0 开案:**判断候选料号在技术上能不能
|
||||
(挂验证任务号或补来源动作)。硬约束 MISMATCH 不许改判,要改只能补证据后重比。
|
||||
|
||||
**存在性检查与数据工单**:启动时逐个料号在 \`parts.profile\` 里查(原 PN + 每个候选)。查无记录 = 「完全没有」,
|
||||
要人从零建整条主数据 —— 发**数据工单**(一张表一张单:P-08 一张、P-10 一张);PN 在库但个别字段缺 = 「有而不全」,
|
||||
归执行工单 01 补。原 PN 缺 = 硬阻塞必须建;候选缺:候选 ≥2 时先问人放弃还是创建,候选 = 1 时必须建。
|
||||
要人从零建整条主数据(表 1 主记录 + 表 2 标准化参数子表 \`parts.parameter\`)—— 发**数据工单**(一张单:P-08 一张、P-10 一张);
|
||||
PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**该不该问人,按候选总数判断,不是按不在库的颗数判断**:
|
||||
原 PN 缺 = 硬阻塞必须建、不问;候选总数 = 1 时那一颗必须建、不问;候选总数 ≥ 2 时,每一颗不在库的候选都要先问人放弃还是创建。
|
||||
**库里都有的话这两项的入场前提本就不成立**(此单不走这条),不会出现在你的「当下可为」里,别发空单。
|
||||
|
||||
**六步走**:绑定与存在性核对 → (等待数据)→ 人补评估输入 → 标准化对照表 → 逐项评估 → 人审结论(可循环)→ 拟落账 → 人放行。
|
||||
@@ -197,7 +200,7 @@ export const NARRATIVE = `§0 开案:**判断候选料号在技术上能不能
|
||||
]}
|
||||
人确认那一下基座已把这一行落进草稿层,**你不许再 write 一遍 customer.profile**。
|
||||
|
||||
② 候选不在库、候选 ≥2 时,一颗一张决策卡(按候选数组顺序,一次只发一张;候选只有一颗时不问、直接建):
|
||||
② 候选不在库、且候选总数 ≥2 时,一颗一张决策卡(按候选数组顺序,一次只发一张;候选总数 = 1 时不问、直接建):
|
||||
{"kind":"decide","itemId":"S0d_p08_master",
|
||||
"ask":"候选 <internal_pn> 在芯片主数据里查无记录。是从零建它的主数据(派数据工单),还是本轮放弃这颗候选?",
|
||||
"options":[{"value":"create","label":"创建主数据(派数据工单)","recommended":true},
|
||||
@@ -209,11 +212,11 @@ export const NARRATIVE = `§0 开案:**判断候选料号在技术上能不能
|
||||
{"kind":"human","itemId":"S0d_p08_master","workOrder":"execution","card":{
|
||||
"title":"数据工单 · 芯片主数据创建(P-08):<料号列表,顿号分隔>",
|
||||
"ask":"这些料号在芯片主数据里查无记录,请在工作区的件上把整条记录建齐(一颗一行,可加行)。",
|
||||
"steps":["每一行:品牌、规格描述、封装、生命周期状态、标准化参数集为必填,缺一格确认不了",
|
||||
"标准化参数集一行一参数:参数名 | 值 | 单位 | hard/soft | 说明;原厂未披露的值写「未披露」,它会进入 UNKNOWN 并挂验证任务",
|
||||
"steps":["表 1 每一行:品牌、规格描述、封装、生命周期状态为必填,缺一格确认不了",
|
||||
"表 2 一行一个参数:所属料号、参数名、值、单位、约束(硬/软)必填,说明是只读注释;原厂未披露的值写「未披露」,它会进入 UNKNOWN 并挂验证任务",
|
||||
"有 datasheet 的把引用与版本一起填(成对);制造商与品牌不一致时分开写",
|
||||
"改完保存,回这张卡确认 —— 确认后这批主数据即入库,活动从等待数据唤醒"],
|
||||
"standard":"每颗料号一行,必填列全齐;参数集至少含输入电压范围 / 输出电压 / 输出电流 / 工作温度 / 封装相关的硬约束项。",
|
||||
"standard":"每颗料号表 1 一行,必填列全齐;表 2 至少含输入电压范围 / 输出电压 / 输出电流 / 工作温度 / 封装相关的硬约束项各一行。",
|
||||
"basis":[{"k":"触发原因","v":"按 internal_pn 查 P-08 查无记录(完全没有,不是字段缺失)"},
|
||||
{"k":"AI 预填","v":"<有 datasheet 就写「已按 datasheet 预填 N 项,请核对」,没有写「未上传 datasheet,请全填」>"}]}}
|
||||
|
||||
@@ -305,31 +308,39 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不
|
||||
},
|
||||
{
|
||||
item: 'S0d_p08_master',
|
||||
intent: '料号不在库:派 P-08 数据工单让人从零建芯片主数据;候选 ≥2 先问放弃还是创建',
|
||||
writes: ['parts.profile', 'parts.document', `${D}.pn_check`],
|
||||
reads: [`${D}.pn_check`, 'candidateInternalPns', 'originalInternalPn', 'datasheets', 'parts.profile', 'parts.document'],
|
||||
intent: '料号不在库:派 P-08 数据工单让人从零建芯片主数据 + 标准化参数子表;候选总数 ≥2 才逐颗问放弃还是创建',
|
||||
writes: ['parts.profile', 'parts.parameter', 'parts.document', `${D}.pn_check`],
|
||||
reads: [`${D}.pn_check`, 'candidateInternalPns', 'originalInternalPn', 'datasheets', 'parts.profile', 'parts.parameter', 'parts.document'],
|
||||
view: 'p08-master-create',
|
||||
tools: [],
|
||||
/* 触达人,不给 doneWhen;人工激活:发卡就算启动 */
|
||||
guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走 —— 都在库时入场前提本就不成立,这一项与下一项 S0d_p10_document 都不会进你的「当下可为」。**先分岔,再动作**:
|
||||
|
||||
【先看轨迹】这一项里已经发过哪些卡、人答了什么(【当前这一步】下面那行引擎事实说得清楚):
|
||||
【先数两个数,再分岔】(先看轨迹:这一项里已经发过哪些卡、人答了什么,【当前这一步】下面那行引擎事实说得清楚)
|
||||
· **候选总数**:入参 \`candidateInternalPns\` 的长度(\`${D}.pn_check\` 里 role = "candidate" 的行数也是它,不随本单进展变化);
|
||||
· **不在库的候选数**:\`${D}.pn_check\` 里 role = "candidate" 且 exists_status = "absent" 的行数(这个数会随着人放弃/创建而变小)。
|
||||
**该不该问人,看的是候选总数,不是不在库的颗数**——**「不在库的只有一颗」不是跳过决策卡的理由**:
|
||||
· 原 PN absent → **硬阻塞**,不问人,直接进入「建」的名单;
|
||||
· 候选 absent 且候选数 ≥2 → 每颗 absent 的候选照说明书②各发一张决策卡(一次一张,按数组顺序);人选 "abandon"/"custom:" 的,
|
||||
把那一行 \`${D}.pn_check\` 改成 exists_status = "abandoned" + abandon_reason(同 PK 覆盖写),这颗不再评估;选 "create" 的进「建」的名单;
|
||||
· 候选 absent 且候选只有 1 颗 → 不问,必须建。
|
||||
**至少留一颗候选**:放弃到只剩最后一颗还是 absent 的候选时,那一颗按「候选只有一颗」办 —— 不再发决策卡问,直接进「建」的名单
|
||||
· **候选总数 = 1** → 那一颗不许放弃,不问人,直接进入「建」的名单;
|
||||
· **候选总数 ≥ 2** → **每一颗 absent 的候选都要先发一张决策卡问人**(一次一张,按候选数组顺序;哪怕这一单不在库的候选只有一颗,
|
||||
只要候选总数 ≥ 2 就要问)照说明书②;人选 "abandon"/"custom:" 的,把那一行 \`${D}.pn_check\` 改成
|
||||
exists_status = "abandoned" + abandon_reason(同 PK 覆盖写),这颗不再评估;选 "create" 的进「建」的名单。
|
||||
**至少留一颗候选**:放弃到只剩最后一颗还是 absent 的候选时,那一颗按「候选总数 = 1」办 —— 不再发决策卡问,直接进「建」的名单
|
||||
(候选全被放弃,存在性核对永远凑不齐、单子会死锁,这一条就是防它)。
|
||||
**别把同一颗候选问两遍**:决策卡答过就按答案办。
|
||||
|
||||
【「建」的名单定了 → 写骨架行,发 P-08 数据工单】
|
||||
1. 给名单里每颗料写一行 \`parts.profile\` 骨架:key {internal_pn},patch 只带你确有依据的列 —— 有 \`datasheets[].text\` 就从里面读
|
||||
brand_name / specification_description / package / lifecycle_status(active/nrnd/eol/discontinued/risk 之一)/ normalized_parameter_set
|
||||
(一行一参数:参数名 | 值 | 单位 | hard/soft | 说明,原厂没写的值写「未披露」)/ datasheet_ref + datasheet_version;没有 datasheet 就只写 internal_pn
|
||||
这一列(其余留给人填,**别编**)。
|
||||
2. 同一拍给每颗料写一行 \`parts.document\` 骨架:key {internal_pn, doc_seq: "01"},patch {document_type: "datasheet", document_status: "pending",
|
||||
【「建」的名单定了 → 写骨架行,发 P-08 数据工单(一张单,件上两张表)】
|
||||
1. 给名单里每颗料写一行 \`parts.profile\` 骨架(表 1):key {internal_pn},patch 只带你确有依据的列 —— 有 \`datasheets[].text\` 就从里面读
|
||||
brand_name / specification_description / package / lifecycle_status(active/nrnd/eol/discontinued/risk 之一)/ datasheet_ref + datasheet_version;
|
||||
没有 datasheet 就只写 internal_pn 这一列(其余留给人填,**别编**)。
|
||||
2. 同一拍给每颗料写标准化参数集骨架(表 2,\`parts.parameter\`,一行一参数):有 \`datasheets[].text\` 就从里面读出典型参数
|
||||
(如输入电压范围 / 输出电压 / 输出电流 / 开关频率 / 静态电流 / 工作温度 / 使能阈值等,按品类而定)各写一行,
|
||||
key {internal_pn, param_name},patch 带 param_value / param_unit / constraint_type(hard/soft)/ param_note(一句话说清这项为什么要卡,
|
||||
如「须覆盖客户输入上限」);原厂没写的值写「未披露」(它会进入 UNKNOWN 并挂验证任务)。没有 datasheet 就只写
|
||||
internal_pn + param_name 骨架(把你判断这颗料该评估的参数名列出来,**param_value 留空给人填,别编**)。
|
||||
3. 同一拍给每颗料写一行 \`parts.document\` 骨架:key {internal_pn, doc_seq: "01"},patch {document_type: "datasheet", document_status: "pending",
|
||||
brand_name 有就写}(这一行是骨架,下一项 S0d_p10_document 会把它填成正式记录)。
|
||||
3. 同一拍发说明书③那张 P-08 数据工单(title 里列全料号),发完这一拍结束(挂起)。
|
||||
4. 同一拍发说明书③那张 P-08 数据工单(title 里列全料号),发完这一拍结束(挂起)。
|
||||
|
||||
【P-08 那张确认回来了】finish 这一项 —— 下一项是 S0d_p10_document,同一批料号接着补资料记录。
|
||||
|
||||
@@ -346,7 +357,7 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不
|
||||
guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走(要走到这一步,S0d_p08_master 必然已经收完);都在库时入场前提本就不成立,
|
||||
这一项与 S0d_p08_master 都不会进你的「当下可为」。
|
||||
|
||||
【发 P-10 数据工单】「建」的名单就是 \`${D}.pn_check\` 里仍是 exists_status = "absent" 的那几行(候选 ≥2 时被放弃的那些,
|
||||
【发 P-10 数据工单】「建」的名单就是 \`${D}.pn_check\` 里仍是 exists_status = "absent" 的那几行(候选总数 ≥2 时被放弃的那些,
|
||||
S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里)。同一拍照说明书④发那张 P-10 数据工单(title 里列全料号,只发一次),
|
||||
发完这一拍结束(挂起)。
|
||||
|
||||
@@ -359,7 +370,7 @@ S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里)
|
||||
item: 'S1_input_confirm',
|
||||
intent: '评估输入清单:绑定四项只读核对,缺口行交 PW 在件上补',
|
||||
writes: [`${D}.input_gap`, `${D}.verdict`, 'demand.request'],
|
||||
reads: [`${D}.input_gap`, `${D}.pn_check`, 'demand.request', 'parts.profile', 'parts.document', 'candidateInternalPns', 'originalInternalPn'],
|
||||
reads: [`${D}.input_gap`, `${D}.pn_check`, 'demand.request', 'parts.profile', 'parts.parameter', 'parts.document', 'candidateInternalPns', 'originalInternalPn'],
|
||||
view: 'eval-input-list',
|
||||
tools: [],
|
||||
/* 交人补缺口,不给 doneWhen;人工激活:发卡就算启动 */
|
||||
@@ -371,7 +382,7 @@ S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里)
|
||||
(候选多颗就 candidate_1、candidate_2… 一颗一行,detail 写料号与在库状态);
|
||||
· 待补两项(status = "gap",detail 写「启动时未收」):application_scenario「应用场景」(source_ledger = "P-01")、bom_position「BOM 位」(source_ledger = "P-09");
|
||||
· 默认可改三项(status = "default",detail 写默认值):substitution_type「替代类型」= "Pin-to-Pin"、rule_version「规则版本」= "${RULE_VERSION}"、owner「负责人 / 期限」= "按客户线默认 · 1 个工作日";
|
||||
· 待确认(status = "gap"):逐颗候选看 \`parts.profile.normalized_parameter_set\`,值是「未披露」的参数各一行(item_key = unknown_<料号>_<序号>,
|
||||
· 待确认(status = "gap"):逐颗候选看它名下 \`parts.parameter\` 的那几行,param_value 是「未披露」的各一行(item_key = unknown_<料号>_<序号>,
|
||||
input_item 如「XL2596S-5.0 使能阈值」,source_ledger = "P-08",detail 写「原厂未披露(有而不全)」);
|
||||
· 齐备项(status = "ok"):原 PN 资料版本(source_ledger = "P-10",detail 写 datasheet 版本)。
|
||||
写完同一拍照说明书⑤发执行工单,发完这一拍结束(挂起)。
|
||||
@@ -388,7 +399,7 @@ S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里)
|
||||
item: 'S2_normalize',
|
||||
intent: '读取并标准化:每个未放弃的候选一条 P-11 主记录骨架 + 逐参数对照行',
|
||||
writes: ['substitution.relation', 'substitution.difference_item'],
|
||||
reads: ['parts.profile', 'parts.document', 'demand.request', `${D}.pn_check`, `${D}.input_gap`, `${D}.verdict`, 'candidateInternalPns', 'originalInternalPn'],
|
||||
reads: ['parts.profile', 'parts.parameter', 'parts.document', 'demand.request', `${D}.pn_check`, `${D}.input_gap`, `${D}.verdict`, 'candidateInternalPns', 'originalInternalPn'],
|
||||
view: 'difference-review',
|
||||
tools: [],
|
||||
doneWhen:
|
||||
@@ -402,10 +413,11 @@ substitution_type = "pin_to_pin"(人在评估输入清单里改了替代类型
|
||||
analysis_status = "ai_draft"、rule_version = "${RULE_VERSION}"、bom_position(照 demand.request 现值抄,没有不写)。
|
||||
recommendation_level / recommendation_reason / difference_summary / risk_level **这一步一格都不写**(S3 的活)。
|
||||
|
||||
【每条关系逐参数写 \`substitution.difference_item\` 对照行】骨架按**原 PN 的参数集**(\`parts.profile.normalized_parameter_set\`,一行一参数
|
||||
「参数名 | 值 | 单位 | hard/soft | 说明」):原 PN 有的每个参数一行 —— difference_item_id = "<关系id>-P01/P02…"、parameter_name、
|
||||
original_value、candidate_value(候选参数集里同名那一行的值;候选没有这个参数 → 写「未披露」)、parameter_unit(以原 PN 为准;候选单位不同先换算,
|
||||
换算不了原值照写、evidence_ref 里注明)、constraint_type(原 PN 那一行的 hard/soft;没标的按口径:封装 / 工艺 / 电压 / 电流 / 温度 / 阈值 hard,其余 soft)、
|
||||
【每条关系逐参数写 \`substitution.difference_item\` 对照行】骨架按**原 PN 在 \`parts.parameter\` 里的那几行**(它是独立子表,一行一参数,
|
||||
按 internal_pn 归属):原 PN 有的每个参数一行 —— difference_item_id = "<关系id>-P01/P02…"、parameter_name(取原 PN 那一行的 param_name)、
|
||||
original_value(取原 PN 那一行的 param_value)、candidate_value(候选在 \`parts.parameter\` 里同 param_name 那一行的 param_value;
|
||||
候选没有这个参数 → 写「未披露」)、parameter_unit(以原 PN 那一行的 param_unit 为准;候选单位不同先换算,
|
||||
换算不了原值照写、evidence_ref 里注明)、constraint_type(取原 PN 那一行的 constraint_type;没标的按口径:封装 / 工艺 / 电压 / 电流 / 温度 / 阈值 hard,其余 soft)、
|
||||
rule_version = "${RULE_VERSION}"、evidence_ref(值从哪来:「P-08 · P-10 <版本>」;本单数据工单建的料号写「人工录入 · 数据工单 · P-08」)。
|
||||
另加两行**封装**与**生命周期**(constraint_type = hard / soft,值分别取 \`package\` 与 \`lifecycle_status\`)。
|
||||
difference_status / compare_result / risk_level / impact_statement / unknown_disposition **这一步不写**。
|
||||
|
||||
@@ -5,12 +5,17 @@
|
||||
* 《SS02-SS03-SS04_Demo台账字段细化与统一方案》/ 四份目标件 / 全流程分支图)。
|
||||
* 骨架照 E04 / I01 已验证的样子:双级入参 → 活动自持单据 + 共享主数据 → decide 循环做中间审核 →
|
||||
* 终局放行。三处与前两个活动刻意不同,写在这里免得下一个读者当成疏漏:
|
||||
* ① **主数据是六张(P-08/P-10/P-01/P-11 主+子/P-06)**,物理表由平台基线
|
||||
* ① **主数据是七张(P-08 主 + P-08 参数子表 / P-10 / P-01 / P-11 主+子 / P-06)**,物理表由平台基线
|
||||
* (`apps/api/src/app/seed-mvp-data.ts`)物化,这里只**追加**本活动要用的列与标记
|
||||
* (required / tally / refersTo / requiredWhen 是活动的授权面;声明合并是纯 add-column)。
|
||||
* **P-08 的标准化参数集是独立子表 `parts.parameter`**(2026-08-27 结构改:用户核对实跑
|
||||
* 页面后指出必须与目标件结构一致——目标件《数据工单-01》本就是两张表,塞进一个文本列
|
||||
* 违反《统一方案》§六「重复明细用子表承载,不在单一文本字段堆叠」)。
|
||||
* ② **数据工单**(PN 不在库时让人从零建整条主数据)= 零输入执行工单 + 工作区的件
|
||||
* (`mine:parts.profile` / `mine:parts.document` 可加行),确认落草稿层、放行并入正式表
|
||||
* (守铁律②;文档里「确认即入库」在本平台落成「无审核环节、放行并入」)。一张表一张单。
|
||||
* (`mine:parts.profile` / `mine:parts.parameter` / `mine:parts.document` 可加行),
|
||||
* 确认落草稿层、放行并入正式表(守铁律②;文档里「确认即入库」在本平台落成「无审核环节、
|
||||
* 放行并入」)。一张台账表一张单(P-08 主记录 + 参数子表算同一张数据工单 —— 目标件是
|
||||
* 同一份工单卡里的两张表)。
|
||||
* ③ 逐项四态与推荐等级**由两枚纯工具算**(规则同时以 D01/D02 两张 DMN 随包冻结当文档),
|
||||
* 模型只负责对照表、证据与影响说明 —— 硬约束 MISMATCH 不可能被措辞改判。
|
||||
*/
|
||||
@@ -84,7 +89,7 @@ export const manifest: ActivityManifest = {
|
||||
identity: {
|
||||
activityId: ACTIVITY_ID,
|
||||
name: 'ACT-WORK-05 · 差异评估',
|
||||
packageVersion: '1.1.1',
|
||||
packageVersion: '1.2.0',
|
||||
category: '需求分析与方案匹配',
|
||||
sdkVersion: '0.1.0-m5',
|
||||
checksum: 'dev-checksum-part-substitution-assessment',
|
||||
@@ -274,7 +279,9 @@ export const manifest: ActivityManifest = {
|
||||
col({ name: 'region', type: 'text', label: '区域', expect: true }),
|
||||
],
|
||||
},
|
||||
/* P-08 芯片主数据:数据工单建整条;参数集是它的一个字段(一张表,不建子表) */
|
||||
/* P-08 芯片主数据:数据工单建整条;标准化参数集已改子表 parts.parameter(2026-08-27,
|
||||
用户看了实跑页面指出「必须与目标件结构一致」——目标件本就是两张表,一张主记录一张
|
||||
参数子表,塞进一个文本列违反《统一方案》§六「重复明细用子表承载」) */
|
||||
{
|
||||
domain: 'parts',
|
||||
object: 'profile',
|
||||
@@ -285,7 +292,6 @@ export const manifest: ActivityManifest = {
|
||||
col({ name: 'brand_name', type: 'text', label: '品牌名称', required: true }),
|
||||
col({ name: 'manufacturer_name', type: 'text', label: '制造商名称', expect: true }),
|
||||
col({ name: 'specification_description', type: 'text', label: '规格描述', required: true }),
|
||||
col({ name: 'normalized_parameter_set', type: 'text', label: '标准化参数集(一行一参数:参数名 | 值 | 单位 | hard/soft | 说明)', required: true }),
|
||||
col({ name: 'package', type: 'text', label: '封装', required: true }),
|
||||
col({ name: 'process_technology', type: 'text', label: '工艺', expect: true }),
|
||||
col({ name: 'lifecycle_status', type: 'text', label: '生命周期状态', required: true, tally: ['active', 'nrnd', 'eol', 'discontinued', 'risk'] }),
|
||||
@@ -295,6 +301,21 @@ export const manifest: ActivityManifest = {
|
||||
col({ name: 'application_scenarios', type: 'text', label: '适配应用场景', expect: true }),
|
||||
],
|
||||
},
|
||||
/* P-08 子表:标准化参数集,一行一参数,internal_pn 是引用墙(查无那颗主记录当场拒) */
|
||||
{
|
||||
domain: 'parts',
|
||||
object: 'parameter',
|
||||
kind: 'master',
|
||||
label: '标准化参数集(P-08 子表)',
|
||||
fields: [
|
||||
col({ name: 'internal_pn', type: 'text', label: '所属料号', primaryKey: true, refersTo: 'parts.profile.internal_pn' }),
|
||||
col({ name: 'param_name', type: 'text', label: '参数名', primaryKey: true }),
|
||||
col({ name: 'param_value', type: 'text', label: '值', required: true }),
|
||||
col({ name: 'param_unit', type: 'text', label: '单位', expect: true }),
|
||||
col({ name: 'constraint_type', type: 'text', label: '约束类型', required: true, tally: ['hard', 'soft'] }),
|
||||
col({ name: 'param_note', type: 'text', label: '说明', expect: true }),
|
||||
],
|
||||
},
|
||||
/* P-10 原厂技术资料库:数据工单建;评估时只读做证据回链 */
|
||||
{
|
||||
domain: 'parts',
|
||||
|
||||
@@ -11,15 +11,15 @@ export const ASSESSMENT_SKILLS: Skill[] = [
|
||||
label: '把多来源资料整成可比对的标准参数对照表',
|
||||
applicability: '输入确认之后、逐项比对之前:原 PN 与每个候选的参数集要摆成同名同单位的一对一。',
|
||||
method: [
|
||||
'参数集在 `parts.profile.normalized_parameter_set` 里,一行一参数:`参数名 | 值 | 单位 | hard/soft | 说明`。',
|
||||
'对照表按**原 PN 的参数名**为骨架:原 PN 有的参数,候选没有对应行 → 候选值写「未披露」(它会成为 UNKNOWN);',
|
||||
'参数集是独立子表 `parts.parameter`(P-08 的结构子表),一行一参数,按 `internal_pn` 归属主记录:列是 param_name / param_value / param_unit / constraint_type / param_note。',
|
||||
'对照表按**原 PN 名下的那几行参数名**为骨架:原 PN 有的参数,候选名下没有同名那一行 → 候选值写「未披露」(它会成为 UNKNOWN);',
|
||||
'候选多出来的参数不比(比对只回答「能不能替」,多出来的能力不构成差异)。',
|
||||
'单位以原 PN 为准:候选单位不同先换算(mV→V、kHz→MHz),换算不了就不改值、在说明里写明 —— 不许静默改数。',
|
||||
'约束类型以原 PN 那一行的 hard/soft 为准;原 PN 没标的按 GOV 口径:封装 / 工艺 / 电气边界(电压 / 电流 / 温度 / 阈值)一律 hard,其余 soft。',
|
||||
'单位以原 PN 那一行的 param_unit 为准:候选单位不同先换算(mV→V、kHz→MHz),换算不了就不改值、在说明里写明 —— 不许静默改数。',
|
||||
'约束类型以原 PN 那一行的 constraint_type 为准;原 PN 没标的按 GOV 口径:封装 / 工艺 / 电气边界(电压 / 电流 / 温度 / 阈值)一律 hard,其余 soft。',
|
||||
'**缺参数不得伪造**:查不到的写「未披露」,不写估计值、不写「约」。',
|
||||
].join('\n'),
|
||||
crystallized: [],
|
||||
knowledgeRefs: ['标准化参数集行语法', '硬/软约束口径'],
|
||||
knowledgeRefs: ['标准化参数子表结构', '硬/软约束口径'],
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
* 五个局面各一张,版面语言**逐块搬自** `docs/SS02-ACT-WORK-05-html/` 的四份目标件
|
||||
* (《HTML报告生成规范》§7:有目标件时「搬」,不要「照着做」——样式表整段搬,标记逐块搬,
|
||||
* 只把写死的数据换成绑定):
|
||||
* · `p08-master-create` —— S0d 摆,数据工单(P-08 芯片主数据),搬自「数据工单-01」;
|
||||
* · `p08-master-create` —— S0d 摆,数据工单(P-08 芯片主数据),搬自「数据工单-01」
|
||||
* (2026-08-27:补齐目标件里原本就有、之前漏搬的【创建什么 · 表 2】标准化参数子表 `parts.parameter`
|
||||
* —— 用户核对实跑页面后指出「页面结构必须与目标件一致」,目标件本就是两张表);
|
||||
* · `p10-document-create` —— S0d 摆,数据工单(P-10 原厂技术资料),**没有自己的目标件**
|
||||
* (规格里数据工单是「一个统一模板按目标台账实例化」),复用「数据工单-01」的样式表与版面骨架;
|
||||
* · `eval-input-list` —— S0/S1 摆,评估输入清单,搬自「执行工单-01」;
|
||||
@@ -108,6 +110,16 @@ ${badgeCss('.stx', [
|
||||
['pending', '待核实', AMBER],
|
||||
['void', '作废', MUTED],
|
||||
])}
|
||||
/*
|
||||
* 表 2(标准化参数集)的约束类型(硬/软)—— 目标件里这一格是字面类名 .ct.hard / .ct.soft
|
||||
* (P08_CSS 已带这两条颜色声明,.ct 基座形状也在);平台的 data-class-of 走的是
|
||||
* v-<token> 别名机制,不是字面枚举值,这里用 badgeCss 生成 .ct.v-hard / .ct.v-soft,
|
||||
* 颜色原样照抄目标件那两条声明的数值(DANGER/MUTED 与 .ct.hard / .ct.soft 同一份值)。
|
||||
*/
|
||||
${badgeCss('.ct', [
|
||||
['hard', '硬约束', DANGER],
|
||||
['soft', '软约束', MUTED],
|
||||
])}
|
||||
`;
|
||||
|
||||
/* ==================== 执行工单(评估输入清单):徽标别名 ==================== */
|
||||
@@ -315,7 +327,6 @@ const P08_TABLE = `<div class="jc-tbl-wrap"><table id="tbl1">
|
||||
<th data-h="Datasheet引用">Datasheet 引用 datasheet_ref</th>
|
||||
<th data-h="Datasheet版本">Datasheet 版本 datasheet_version<span class="thd">与引用成对</span></th>
|
||||
<th data-h="适配应用场景">适配场景 application_scenarios<span class="thd">判断实际适用性</span></th>
|
||||
<th class="req" data-h="标准化参数集">标准化参数集 normalized_parameter_set<span class="thd">一行一参数:参数名|值|单位|hard/soft|说明</span></th>
|
||||
</tr></thead>
|
||||
<tbody data-rows="mine:parts.profile" data-edit="parts.profile"><tr>
|
||||
<td class="rn"><span class="jc-rn-num"></span><button class="jc-rowdel" type="button" title="删除此行" data-edit-del="parts.profile">×</button></td>
|
||||
@@ -330,7 +341,34 @@ const P08_TABLE = `<div class="jc-tbl-wrap"><table id="tbl1">
|
||||
<td>${cell('datasheet_ref', 'mono')}</td>
|
||||
<td>${cell('datasheet_version')}</td>
|
||||
<td>${cell('application_scenarios')}</td>
|
||||
<td><span class="jc-vcell mono" data-slot="normalized_parameter_set" data-blank></span><textarea class="jc-cell mono" data-col="normalized_parameter_set" placeholder="输入电压范围 | 4.5~40 | V | hard | 客户上限 40V 输出电压 | 5.0 | V | hard | 固定版 使能阈值 | 未披露 | V | hard | 原厂未给"></textarea></td>
|
||||
</tr></tbody>
|
||||
</table></div>`;
|
||||
|
||||
/*
|
||||
* 表 2 · 标准化参数集(P-08 子表 `parts.parameter`)—— 粒度与表 1 不同(一个参数一行,
|
||||
* 行数不固定),不能塞进表 1 的一格(《统一方案》§六:重复明细用子表承载),
|
||||
* 逐块搬自目标件《数据工单-01》的【创建什么 · 表 2】,`id="tbl2"` 沿用目标件自己的
|
||||
* 列宽声明(P08_CSS 里 `#tbl2 th:nth-child(n)` 那几条,不必在这里重复)。
|
||||
* 「说明」列是系统只读注释(目标件原话:「说明列为系统注释,无需填写」),不给 data-col。
|
||||
*/
|
||||
const P08_PARAMS_TABLE = `<div class="jc-tbl-wrap"><table id="tbl2">
|
||||
<thead><tr>
|
||||
<th style="width:38px">#</th>
|
||||
<th class="req" data-h="所属料号">所属料号 internal_pn<span class="thd">归属表 1 哪条记录</span></th>
|
||||
<th class="req" data-h="参数名">参数名</th>
|
||||
<th class="req" data-h="值">值</th>
|
||||
<th data-h="单位">单位</th>
|
||||
<th class="req" data-h="约束类型">约束(硬/软)</th>
|
||||
<th data-h="说明">说明</th>
|
||||
</tr></thead>
|
||||
<tbody data-rows="mine:parts.parameter" data-edit="parts.parameter"><tr>
|
||||
<td class="rn"><span class="jc-rn-num"></span><button class="jc-rowdel" type="button" title="删除此行" data-edit-del="parts.parameter">×</button></td>
|
||||
<td>${cell('internal_pn', 'mono')}</td>
|
||||
<td>${cell('param_name')}</td>
|
||||
<td>${cell('param_value', 'mono')}</td>
|
||||
<td>${cell('param_unit')}</td>
|
||||
<td>${badge('ct', 'constraint_type', true)}${select('constraint_type', [['hard', '硬约束'], ['soft', '软约束']], true)}</td>
|
||||
<td class="desc">${ro('param_note')}</td>
|
||||
</tr></tbody>
|
||||
</table></div>`;
|
||||
|
||||
@@ -347,16 +385,21 @@ export const P08_MASTER_CREATE = doc({
|
||||
body:
|
||||
zone('做什么', '批量创建 P-08 主数据记录', ZIC_TASK) +
|
||||
note([
|
||||
'<span class="need">模板定位</span>:本单是「数据工单」统一模板的实例——<b>一单一类(可多条)</b>:目标台账 = <b>芯片主数据(P-08)</b>;<b>字段即表头、每行一条新记录</b>;标准化参数集是这条记录的一个字段(一行一参数)。',
|
||||
'<span class="need">模板定位</span>:本单是「数据工单」统一模板的实例——<b>一单一类(可多条)</b>:目标台账 = <b>芯片主数据(P-08)</b>;<b>字段即表头、每行一条新记录</b>;标准化参数集是它的结构子表(表 2,一行一参数,按所属料号归属)。',
|
||||
'<span class="need">触发原因</span>:系统按内部料号查芯片主数据<b>查无记录</b>——是「完全没有」,不是字段缺失(那归执行工单)。',
|
||||
'<span class="done">AI 预填</span>:上传了 datasheet 的料号 AI 已按资料预填,标着值的格子请核对;没上传的料号全部手填,同为合法路径。',
|
||||
'<span class="need">纪律</span>:必填列全齐才允许确认;确认后这批记录进本单草稿,放行时并入正式库并留痕(确认人 = 数据责任人);只建主数据,验证记录等事实类不在此建。',
|
||||
]) +
|
||||
zone('创建什么', '主数据记录(每行一条,*为必填列)', ZIC_TABLE) +
|
||||
zone('创建什么 · 表 1', '主数据记录(每行一条,*为必填列)', ZIC_TABLE) +
|
||||
gapbar('parts_profile', '必填') +
|
||||
editGate('parts.profile', `<b>待建 <span data-slot="parts_profile_rows"></span> 条记录</b>,点「编辑」核对预填、补齐空缺;「+ 加一行」= 再建一条`) +
|
||||
P08_TABLE +
|
||||
addrow('parts.profile', '+ 加一行(再建一条记录)', '新行的内部料号必须写(它是这一行的钥匙);其余必填列全部填写后才允许确认;删一行 = 该记录不建') +
|
||||
`<p class="zone tbl-gap">${ZIC_TABLE}<b>【创建什么 · 表 2】</b>—— 标准化参数集(每行一个参数,按所属料号归属,*必填)</p>` +
|
||||
gapbar('parts_parameter', '必填') +
|
||||
editGate('parts.parameter', `<b>已铺 <span data-slot="parts_parameter_rows"></span> 行参数</b>(有 datasheet 的已按资料预填),点「编辑」核对修正;「+ 加一行」补充未列出的参数`) +
|
||||
P08_PARAMS_TABLE +
|
||||
addrow('parts.parameter', '+ 加一行(补充参数)', '所属料号 / 参数名 / 值 / 单位 / 约束类型齐全;「未披露」请直接写未披露,将进入 UNKNOWN 并挂验证任务;「说明」列为系统注释,无需填写') +
|
||||
flow('<b>提交 / 确认:</b>本工单只承载内容;确认请在对话区的【执行卡】中进行。确认后活动自动从「等待数据」唤醒。'),
|
||||
});
|
||||
|
||||
|
||||
@@ -19,10 +19,11 @@ describe('主数据四域', () => {
|
||||
expect([...new Set(MASTER_OBJECTS.map((o) => o.domain))].sort()).toEqual([...MASTER_DOMAINS].sort());
|
||||
});
|
||||
|
||||
it('SS02 差异评估要读写的六张主数据表都在基线里,主键照《统一方案》', () => {
|
||||
it('SS02 差异评估要读写的七张主数据表都在基线里,主键照《统一方案》', () => {
|
||||
const pk = (domain: string, object: string) =>
|
||||
MASTER_OBJECTS.find((o) => o.domain === domain && o.object === object)?.fields.filter((f) => f.primaryKey).map((f) => f.name);
|
||||
expect(pk('parts', 'profile')).toEqual(['internal_pn']);
|
||||
expect(pk('parts', 'parameter')).toEqual(['internal_pn', 'param_name']);
|
||||
expect(pk('parts', 'document')).toEqual(['internal_pn', 'doc_seq']);
|
||||
expect(pk('demand', 'request')).toEqual(['request_id']);
|
||||
expect(pk('substitution', 'relation')).toEqual(['substitution_relation_id']);
|
||||
@@ -36,6 +37,21 @@ describe('主数据四域', () => {
|
||||
expect(parts).not.toContain('XL2596S-5.0');
|
||||
});
|
||||
|
||||
it('样例料号的标准化参数集是独立子表 parts.parameter:两颗料各 7 行,主键是 (internal_pn, param_name)', () => {
|
||||
const params = MASTER_SAMPLE_ROWS.filter((r) => r.address.domain === 'parts' && r.address.object === 'parameter');
|
||||
const byPn = (pn: string) => params.filter((r) => r.key['internal_pn'] === pn);
|
||||
expect(byPn('LM2596S-5.0')).toHaveLength(7);
|
||||
expect(byPn('LM2596S-ADJ')).toHaveLength(7);
|
||||
for (const row of params) {
|
||||
expect(Object.keys(row.key).sort()).toEqual(['internal_pn', 'param_name']);
|
||||
expect(row.values['param_value'], JSON.stringify(row.key)).toBeTruthy();
|
||||
expect(row.values['constraint_type']).toMatch(/^(hard|soft)$/);
|
||||
}
|
||||
// parts.profile 不再声明这一列的值 —— 两行样例主记录里它应为空(物理列留在基线,不再写)
|
||||
const profiles = MASTER_SAMPLE_ROWS.filter((r) => r.address.domain === 'parts' && r.address.object === 'profile');
|
||||
for (const row of profiles) expect(row.values['normalized_parameter_set']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('四个域各自都有表 —— 空占位也得立得住', () => {
|
||||
for (const d of MASTER_DOMAINS) {
|
||||
expect(MASTER_OBJECTS.filter((o) => o.domain === d).length, `${d} 域一张表都没有`).toBeGreaterThan(0);
|
||||
|
||||
@@ -143,17 +143,22 @@ export const MASTER_OBJECTS: readonly ObjectDeclaration[] = [
|
||||
f('trend', 'text', '走势'),
|
||||
]),
|
||||
|
||||
/* ── 料件:P-08 芯片主数据 / P-10 原厂技术资料库(SS02 差异评估 2026-08-26 加)──
|
||||
/* ── 料件:P-08 芯片主数据 / P-10 原厂技术资料库(SS02 差异评估 2026-08-26 加,2026-08-27 结构改)──
|
||||
字段名与《SS02-SS03-SS04_Demo台账字段细化与统一方案》§4.2 一字不差;全链路只认 internal_pn。
|
||||
参数集是 P-08 的一个字段(用户定:一张表,不建子表),一行一参数:
|
||||
`参数名 | 值 | 单位 | hard/soft | 说明`。老的 market.part(主键 pn)不复用,记 debt 待并。 */
|
||||
标准化参数集**改为独立子表 `parts.parameter`**(2026-08-27 用户当场核对实跑页面后指出:
|
||||
「做出来的页面结构必须与目标件一致」——目标件《ACT-WORK-05-数据工单-01》本就是两张表,
|
||||
表 1 一个料一行(主记录)、表 2 一个参数一行(子表,按所属料号归属);塞进主记录的一个文本
|
||||
列违反《统一方案》§六「重复明细用子表承载,不在单一文本字段堆叠」,§4.2 里
|
||||
`normalized_parameter_set` 的数据类型本就写的是 `Structure`。物理列 `parts.profile.
|
||||
normalized_parameter_set` 仍留在基线里(不删列),只是活动清单不再声明它、不再写它。
|
||||
老的 market.part(主键 pn)不复用,记 debt 待并。 */
|
||||
master('parts', 'profile', '芯片主数据(P-08)', [
|
||||
f('internal_pn', 'text', '内部料号', true),
|
||||
f('customer_pn', 'text', '客户料号'),
|
||||
f('brand_name', 'text', '品牌名称'),
|
||||
f('manufacturer_name', 'text', '制造商名称'),
|
||||
f('specification_description', 'text', '规格描述'),
|
||||
f('normalized_parameter_set', 'text', '标准化参数集(一行一参数:参数名 | 值 | 单位 | hard/soft | 说明)'),
|
||||
f('normalized_parameter_set', 'text', '标准化参数集(已改子表 parts.parameter,这一列不再声明/不再写)'),
|
||||
f('package', 'text', '封装'),
|
||||
f('process_technology', 'text', '工艺'),
|
||||
f('lifecycle_status', 'text', '生命周期状态'),
|
||||
@@ -163,6 +168,15 @@ export const MASTER_OBJECTS: readonly ObjectDeclaration[] = [
|
||||
f('application_scenarios', 'text', '适配应用场景'),
|
||||
f('pin_to_pin_relation_ref', 'text', 'Pin-to-Pin 关系引用'),
|
||||
]),
|
||||
/* P-08 子表:标准化参数集,一行一参数,按 internal_pn 归属主记录(《统一方案》§六:重复明细用子表) */
|
||||
master('parts', 'parameter', '标准化参数集(P-08 子表)', [
|
||||
f('internal_pn', 'text', '所属料号', true),
|
||||
f('param_name', 'text', '参数名', true),
|
||||
f('param_value', 'text', '值'),
|
||||
f('param_unit', 'text', '单位'),
|
||||
f('constraint_type', 'text', '约束类型'),
|
||||
f('param_note', 'text', '说明'),
|
||||
]),
|
||||
master('parts', 'document', '原厂技术资料库(P-10)', [
|
||||
f('internal_pn', 'text', '关联内部料号', true),
|
||||
f('doc_seq', 'text', '资料序号(同一料号下 01/02…)', true),
|
||||
@@ -462,10 +476,11 @@ export const MASTER_SAMPLE_ROWS: readonly { address: LedgerAddress; key: LedgerK
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ── SS02 差异评估的样例料号(2026-08-26)──
|
||||
* ── SS02 差异评估的样例料号(2026-08-26,2026-08-27 参数集改子表)──
|
||||
* 只播**原 PN**与**同族可调版**两颗:「都在库」那条路靠它们;候选 XL2596S-5.0 与国产 DCDC
|
||||
* **刻意不播** —— 「候选缺库 → 派数据工单」「硬约束不推荐」两条路要试得出来,库里得真没有。
|
||||
* 参数集一行一参数(参数名 | 值 | 单位 | hard/soft | 说明),与 P-08 声明的行语法同一份。
|
||||
* 标准化参数集是独立子表 `parts.parameter`,一颗料 7 行(与目标件《数据工单-01》的
|
||||
* 表 2 示例预填同形);数值照旧版 `normalized_parameter_set` 文本行逐条搬,一个字不改。
|
||||
*/
|
||||
{
|
||||
address: { domain: 'parts', object: 'profile' },
|
||||
@@ -475,15 +490,6 @@ export const MASTER_SAMPLE_ROWS: readonly { address: LedgerAddress; key: LedgerK
|
||||
brand_name: 'TI',
|
||||
manufacturer_name: 'Texas Instruments',
|
||||
specification_description: '降压型 DCDC 稳压器,3A,固定 5.0V,150kHz',
|
||||
normalized_parameter_set: [
|
||||
'输入电压范围 | 4.5~40 | V | hard | 客户输入上限 40V',
|
||||
'输出电压 | 5.0 | V | hard | 固定版',
|
||||
'输出电流 | 3 | A | hard | 满载能力',
|
||||
'开关频率 | 150 | kHz | hard | 影响外围电感/电容',
|
||||
'静态电流 | 5 | mA | soft | 待机功耗',
|
||||
'工作温度 | -40~+85 | ℃ | hard | 工业温域',
|
||||
'使能阈值 | 1.4 | V | hard | ON/OFF 引脚阈值',
|
||||
].join('\n'),
|
||||
package: 'TO-263-5',
|
||||
process_technology: 'BCD',
|
||||
lifecycle_status: 'nrnd',
|
||||
@@ -493,6 +499,13 @@ export const MASTER_SAMPLE_ROWS: readonly { address: LedgerAddress; key: LedgerK
|
||||
application_scenarios: '工业电源;消费电源',
|
||||
},
|
||||
},
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-5.0', param_name: '输入电压范围' }, values: { internal_pn: 'LM2596S-5.0', param_name: '输入电压范围', param_value: '4.5~40', param_unit: 'V', constraint_type: 'hard', param_note: '客户输入上限 40V' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-5.0', param_name: '输出电压' }, values: { internal_pn: 'LM2596S-5.0', param_name: '输出电压', param_value: '5.0', param_unit: 'V', constraint_type: 'hard', param_note: '固定版' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-5.0', param_name: '输出电流' }, values: { internal_pn: 'LM2596S-5.0', param_name: '输出电流', param_value: '3', param_unit: 'A', constraint_type: 'hard', param_note: '满载能力' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-5.0', param_name: '开关频率' }, values: { internal_pn: 'LM2596S-5.0', param_name: '开关频率', param_value: '150', param_unit: 'kHz', constraint_type: 'hard', param_note: '影响外围电感/电容' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-5.0', param_name: '静态电流' }, values: { internal_pn: 'LM2596S-5.0', param_name: '静态电流', param_value: '5', param_unit: 'mA', constraint_type: 'soft', param_note: '待机功耗' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-5.0', param_name: '工作温度' }, values: { internal_pn: 'LM2596S-5.0', param_name: '工作温度', param_value: '-40~+85', param_unit: '℃', constraint_type: 'hard', param_note: '工业温域' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-5.0', param_name: '使能阈值' }, values: { internal_pn: 'LM2596S-5.0', param_name: '使能阈值', param_value: '1.4', param_unit: 'V', constraint_type: 'hard', param_note: 'ON/OFF 引脚阈值' } },
|
||||
{
|
||||
address: { domain: 'parts', object: 'profile' },
|
||||
key: { internal_pn: 'LM2596S-ADJ' },
|
||||
@@ -501,15 +514,6 @@ export const MASTER_SAMPLE_ROWS: readonly { address: LedgerAddress; key: LedgerK
|
||||
brand_name: 'TI',
|
||||
manufacturer_name: 'Texas Instruments',
|
||||
specification_description: '降压型 DCDC 稳压器,3A,可调输出 1.23~37V,150kHz(LM2596 同族)',
|
||||
normalized_parameter_set: [
|
||||
'输入电压范围 | 4.5~40 | V | hard | 与固定版同',
|
||||
'输出电压 | 1.23~37 | V | hard | 可调版,靠外围反馈网络定',
|
||||
'输出电流 | 3 | A | hard | 满载能力',
|
||||
'开关频率 | 150 | kHz | hard | 影响外围电感/电容',
|
||||
'静态电流 | 5 | mA | soft | 待机功耗',
|
||||
'工作温度 | -40~+85 | ℃ | hard | 工业温域',
|
||||
'使能阈值 | 1.4 | V | hard | ON/OFF 引脚阈值',
|
||||
].join('\n'),
|
||||
package: 'TO-263-5',
|
||||
process_technology: 'BCD',
|
||||
lifecycle_status: 'active',
|
||||
@@ -518,6 +522,13 @@ export const MASTER_SAMPLE_ROWS: readonly { address: LedgerAddress; key: LedgerK
|
||||
application_scenarios: '工业电源;消费电源',
|
||||
},
|
||||
},
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-ADJ', param_name: '输入电压范围' }, values: { internal_pn: 'LM2596S-ADJ', param_name: '输入电压范围', param_value: '4.5~40', param_unit: 'V', constraint_type: 'hard', param_note: '与固定版同' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-ADJ', param_name: '输出电压' }, values: { internal_pn: 'LM2596S-ADJ', param_name: '输出电压', param_value: '1.23~37', param_unit: 'V', constraint_type: 'hard', param_note: '可调版,靠外围反馈网络定' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-ADJ', param_name: '输出电流' }, values: { internal_pn: 'LM2596S-ADJ', param_name: '输出电流', param_value: '3', param_unit: 'A', constraint_type: 'hard', param_note: '满载能力' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-ADJ', param_name: '开关频率' }, values: { internal_pn: 'LM2596S-ADJ', param_name: '开关频率', param_value: '150', param_unit: 'kHz', constraint_type: 'hard', param_note: '影响外围电感/电容' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-ADJ', param_name: '静态电流' }, values: { internal_pn: 'LM2596S-ADJ', param_name: '静态电流', param_value: '5', param_unit: 'mA', constraint_type: 'soft', param_note: '待机功耗' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-ADJ', param_name: '工作温度' }, values: { internal_pn: 'LM2596S-ADJ', param_name: '工作温度', param_value: '-40~+85', param_unit: '℃', constraint_type: 'hard', param_note: '工业温域' } },
|
||||
{ address: { domain: 'parts', object: 'parameter' }, key: { internal_pn: 'LM2596S-ADJ', param_name: '使能阈值' }, values: { internal_pn: 'LM2596S-ADJ', param_name: '使能阈值', param_value: '1.4', param_unit: 'V', constraint_type: 'hard', param_note: 'ON/OFF 引脚阈值' } },
|
||||
{
|
||||
address: { domain: 'parts', object: 'document' },
|
||||
key: { internal_pn: 'LM2596S-5.0', doc_seq: '01' },
|
||||
|
||||
+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