diff --git a/activities/part-substitution-assessment/__tests__/activity.test.ts b/activities/part-substitution-assessment/__tests__/activity.test.ts index fcd487b..f934efd 100644 --- a/activities/part-substitution-assessment/__tests__/activity.test.ts +++ b/activities/part-substitution-assessment/__tests__/activity.test.ts @@ -13,8 +13,14 @@ import { manifest, D, RULE_VERSION } from '../src/manifest.ts'; import activity from '../src/index.ts'; const ITEMS = ['S0_bind', 'S0d_p08_master', 'S0d_p10_document', 'S1_input_confirm', 'S2_normalize', 'S3_assess', 'S4_review', 'S5_prepare_landing', 'T_release']; -const NO_DONE_WHEN = new Set(['S0d_p08_master', 'S0d_p10_document', 'S1_input_confirm', 'S4_review', 'T_release']); +/* + * 2026-08-27 晚:两张数据工单改成**给 doneWhen**(只读入库事实)—— 引擎只在人答过卡之后求它, + * 不再靠模型记得 finish。仍不给的三处:输入确认(交人补缺口)/ 审核循环 / 放行。 + */ +const NO_DONE_WHEN = new Set(['S1_input_confirm', 'S4_review', 'T_release']); const VIEWS = ['p08-master-create', 'p10-document-create', 'eval-input-list', 'difference-review', 'landing-confirm']; +/** 运营中心 L2 沙箱里那张关键台账数据看板 —— 不是工单件,只在「都声明了」这一条里出现;自己那组断言在文件尾 */ +const BOARD_VIEW = 'activity-ledger'; const byAddress = (domain: string, object: string) => manifest.declarations.ledger.find((l) => l.domain === domain && l.object === object); const viewHtml = (name: string) => manifest.assets.viewTemplates.find((t) => t.name === name)?.html ?? ''; @@ -67,8 +73,8 @@ describe('清单本身合规', () => { expect(newCustomer).toBeDefined(); }); - it('五张视图模版都声明了,剧本每一步引用的 view 都在其中', () => { - expect(new Set(manifest.assets.viewTemplates.map((v) => v.name))).toEqual(new Set(VIEWS)); + it('五张工单视图 + 一张运营侧看板都声明了,剧本每一步引用的 view 都在其中', () => { + expect(new Set(manifest.assets.viewTemplates.map((v) => v.name))).toEqual(new Set([...VIEWS, BOARD_VIEW])); for (const step of manifest.assets.businessModel.steps as ReadonlyArray<{ view?: string }>) { if (step.view !== undefined) expect(VIEWS, step.view).toContain(step.view); } @@ -158,7 +164,7 @@ describe('案例模型与清单的对齐', () => { expect((manifest.assets.businessModel.steps as ReadonlyArray<{ item: string }>).map((s) => s.item)).toEqual(ITEMS); }); - it('五处不给 doneWhen(两张数据工单 / 输入确认 / 审核 / 放行),其余四处都给', () => { + it('三处不给 doneWhen(输入确认 / 审核 / 放行),其余六处都给 —— 两张数据工单的只读入库事实', () => { for (const s of manifest.assets.businessModel.steps as ReadonlyArray<{ item: string; doneWhen?: string }>) { if (NO_DONE_WHEN.has(s.item)) expect(s.doneWhen, s.item).toBeUndefined(); else expect(s.doneWhen, s.item).toBeDefined(); @@ -313,6 +319,23 @@ describe('徽标别名与值域相关的坑(2026-08-27 实测账,回归网)', () { view: 'landing-confirm', domain: 'substitution', object: 'relation', column: 'analysis_status' }, { view: 'landing-confirm', domain: 'substitution', object: 'relation', column: 'risk_level' }, { view: 'landing-confirm', domain: 'demand', object: 'request', column: 'request_status' }, + /* 运营侧看板把六张表的枚举列全摆成徽标 —— 每一列的每个值都得有文案,漏一个就是一枚「—」 */ + { view: BOARD_VIEW, domain: 'parts', object: 'profile', column: 'lifecycle_status' }, + { view: BOARD_VIEW, domain: 'parts', object: 'document', column: 'document_type' }, + { view: BOARD_VIEW, domain: 'parts', object: 'document', column: 'document_status' }, + { view: BOARD_VIEW, domain: 'demand', object: 'request', column: 'request_status' }, + { view: BOARD_VIEW, domain: 'demand', object: 'request', column: 'risk_level' }, + { view: BOARD_VIEW, domain: 'substitution', object: 'relation', column: 'substitution_type' }, + { view: BOARD_VIEW, domain: 'substitution', object: 'relation', column: 'recommendation_level' }, + { view: BOARD_VIEW, domain: 'substitution', object: 'relation', column: 'analysis_status' }, + { view: BOARD_VIEW, domain: 'substitution', object: 'relation', column: 'risk_level' }, + { view: BOARD_VIEW, domain: 'substitution', object: 'difference_item', column: 'constraint_type' }, + { view: BOARD_VIEW, domain: 'substitution', object: 'difference_item', column: 'difference_status' }, + { view: BOARD_VIEW, domain: 'substitution', object: 'difference_item', column: 'risk_level' }, + { view: BOARD_VIEW, domain: 'validation', object: 'record', column: 'validation_type' }, + { view: BOARD_VIEW, domain: 'validation', object: 'record', column: 'validation_status' }, + { view: BOARD_VIEW, domain: D, object: 'pn_check', column: 'role' }, + { view: BOARD_VIEW, domain: D, object: 'pn_check', column: 'exists_status' }, ]; it('每张模版摆了徽标的每个 tally 列,声明里的每个值都有对应的 v- 规则(选择器是从值算出来的,不是手写的)', () => { @@ -400,3 +423,134 @@ const CONTRACT = { prohibitions: [], discretion: [], } as const; + +/* 验收件题头是验收绿(2026-08-27 用户定:与对话区验收卡同色,不再是目标件的红) */ +describe('两张验收件的主题色', () => { + it('ACC1 / ACC2 的 --ledger 是验收绿 #0aa294,题头渐变不再带红;CONDITIONAL 徽标保留语义红', async () => { + const { ACC1_CSS, ACC2_CSS } = await import('../src/views/target-style.ts'); + for (const css of [ACC1_CSS, ACC2_CSS]) { + expect(css).toContain('--ledger:#0aa294'); + expect(css).not.toContain('--ledger:#b23a2e'); + expect(css).not.toContain('#fdf3f1'); + expect(css).toContain('rgba(10,162,148,.25)'); + } + // ACC2 的 CONDITIONAL 徽标保留目标件的语义红;ACC1 目标件 2026-08-27 改版把它改成了品牌蓝,照抄 + expect(ACC2_CSS).toContain('.st.cond{color:#b23a2e'); + expect(ACC1_CSS).toContain('.st.cond{color:var(--brand-deep)'); + }); +}); + +/* + * 差异评估结论审核 · 目标件 2026-08-27 改版:「一个候选一张表」(用户:存在多个替代 PN 要对比差异时看不清楚)。 + * 外层按 relation 逐个复制
,里层明细只列该候选名下的行(data-by 父键过滤); + * 汇总表一个候选一条;品牌从 parts.profile 整表按料号挑(取值,不是算)。 + */ +describe('difference-review:一个候选一张表', () => { + const html = viewHtml('difference-review'); + it('候选块是嵌套行模版:外层 relation、里层明细按 substitution_relation_id 过滤;容器标签不在行模版里再出现', () => { + expect(html).toContain('
'); + expect(html).toContain('data-rows="mine:substitution.difference_item" data-by="substitution_relation_id"'); + const inner = html.slice(html.indexOf('
'), html.indexOf('
')); + expect(inner).not.toContain(' { + expect(html).toContain(''); + expect(html).toContain(''); + expect(html).toContain('class="stat" data-slot="difference_summary"'); + expect(html).toContain('data-rows="parts.profile" data-by="internal_pn=candidate_internal_pn"'); + expect(html).toContain('data-slot="substitution_relation_rows"'); + expect(html).not.toContain('【AI 产出的差异明细】'); + }); +}); + +/* 评估输入清单的件上摆「候选参数现值」:一颗料一张、里层按料号过滤、只读(没有 data-edit) */ +describe('eval-input-list:候选参数现值只读参考', () => { + it('外层按核对表逐颗复制,里层 parts.parameter 按 internal_pn 过滤,不是可编辑表', () => { + const html = viewHtml('eval-input-list'); + expect(html).toContain(`
`); + expect(html).toContain(''); + const block = html.slice(html.indexOf('
')); + expect(block).not.toContain('data-edit='); + expect(block).toContain('data-slot="param_value"'); + expect(html).toContain('【候选参数现值】'); + }); +}); + +/* + * 运营中心沙箱看板(2026-08-27 用户定:替换全流程分支图,只摆最近一次已放行实例的关键台账落账快照)。 + * 模版归活动,「哪几行是这一单的」也归活动 —— `data` 声明:案号怎么铸、哪张表按哪一列圈;基座只照它查正式表。 + */ +describe('activity-ledger:关键台账数据看板', () => { + const board = manifest.assets.viewTemplates.find((v) => v.name === BOARD_VIEW); + const html = board?.html ?? ''; + const data = board?.data; + const declaredAddresses = new Set(manifest.declarations.ledger.map((l) => `${l.domain}.${l.object}`)); + + it('取数声明:案号台账是 record 且 key 是它的 caseKey、前缀 W05-;每条 rows 的表都已声明;from 只指 case 或前面的 as.列', () => { + expect(data).toBeDefined(); + if (data === undefined) return; + const caseLedger = manifest.declarations.ledger.find((l) => `${l.domain}.${l.object}` === data.case.ledger); + expect(caseLedger?.kind).toBe('record'); + expect(caseLedger?.caseKey).toBe(data.case.key); + expect(data.case.prefix).toBe('W05-'); + const seen = new Set(); + for (const entry of data.rows) { + expect(declaredAddresses.has(entry.ledger), entry.ledger).toBe(true); + const columns = new Set(manifest.declarations.ledger.find((l) => `${l.domain}.${l.object}` === entry.ledger)?.fields.map((f) => f.name)); + expect(columns.has(entry.by.column), `${entry.as} by ${entry.by.column}`).toBe(true); + for (const ref of [entry.by.from, entry.not?.from]) { + if (ref === undefined || ref === 'case') continue; + const at = ref.lastIndexOf('.'); + const as = ref.slice(0, at); + const column = ref.slice(at + 1); + expect(seen.has(as), `${entry.as} 引用了后面 / 不存在的集合 ${as}`).toBe(true); + const source = data.rows.find((r) => r.as === as); + const sourceColumns = new Set(manifest.declarations.ledger.find((l) => `${l.domain}.${l.object}` === source?.ledger)?.fields.map((f) => f.name)); + expect(sourceColumns.has(column), `${ref} 不是 ${source?.ledger} 的列`).toBe(true); + } + seen.add(entry.as); + } + // 六张主表 + 案号两张 + 同料号历史都圈了 + expect([...seen]).toEqual( + expect.arrayContaining([`${D}.verdict`, `${D}.pn_check`, 'demand.request', 'customer.profile', 'parts.profile', 'parts.parameter', 'parts.document', 'substitution.relation', 'substitution.difference_item', 'validation.record', 'validation.history']), + ); + }); + + it('html 里每个 data-rows 键都是声明里的一个 as(行由基座圈过,不是整表);嵌套只用 data-by 父键过滤', () => { + const as = new Set(data?.rows.map((r) => r.as) ?? []); + const keys = [...html.matchAll(/data-rows="([^"]+)"/g)].map((m) => m[1] ?? ''); + expect(keys.length).toBeGreaterThan(8); + for (const key of keys) expect(as.has(key), key).toBe(true); + expect(html).toContain(''); + expect(html).toContain('
'); + expect(html).toContain(''); + expect(html).not.toContain('mine:'); + }); + + it('无脚本、无 on*、无外链(过安装静态审);有 给出口注入报高脚本;三态由 board:state 投影;只读', () => { + expect(html.length).toBeGreaterThan(5000); + expect(html).not.toMatch(/<\s*script\b/i); + expect(html).not.toMatch(/\son[a-z]+\s*=/i); + expect(html).not.toMatch(/(src|href)\s*=\s*["']?\s*(https?:)?\/\//i); + expect(html.toLowerCase()).toContain(''); + expect(html).toContain('
'); + expect(html).toContain('.page.v-none .notice.none{display:flex}'); + expect(html).toContain('.page.v-missing .notice.missing{display:flex}'); + expect(html).not.toContain('data-edit='); + expect(html).not.toContain('data-col='); + }); + + it('目标件六张表的表头与页头要素都在;MATCH 行淡显、放弃的候选只在有 abandoned 时露面 —— 都是 class 交样式表,模版不判', () => { + for (const header of ['内部料号', '替代关系 ID', '参数项', '关联料号', '验证 ID', '标准化参数集']) expect(html).toContain(header); + expect(html).toContain('data-slot="case:instanceNo"'); + expect(html).toContain('data-slot="case:ref"'); + expect(html).toContain(`data-slot="${D}.verdict.original_internal_pn"`); + expect(html).toContain('data-slot="demand.request.request_id"'); + expect(html).toContain(''); + expect(html).toContain('tr.v-match td{opacity:.55}'); + expect(html).toContain('.aband:has(i.v-abandoned){display:block}'); + expect(html).toContain('data-slot="substitution_difference_item_difference_status_unknown"'); + }); +}); diff --git a/activities/part-substitution-assessment/__tests__/steps.test.ts b/activities/part-substitution-assessment/__tests__/steps.test.ts index 9170823..1f2b3bc 100644 --- a/activities/part-substitution-assessment/__tests__/steps.test.ts +++ b/activities/part-substitution-assessment/__tests__/steps.test.ts @@ -174,6 +174,45 @@ describe('说明书与剧本的骨架', () => { expect(secondPass).toMatch(/收不了步|收不掉/); }); + /* + * 数据工单确认即入库(2026-08-27 用户定:「数据工单里填的数据,确认后就应该入库」)。 + * 基座在确认那一刻把卡上 gateLedgers 那几张表并进正式表;剧本这一头不能再教模型「已经落草稿层」—— + * 那句话会让它以为放行前还能改、还能 drop,而在库的键写下来会被当场拒。 + */ + it('两张数据工单的剧本都写明:确认后已入正式表,别再写、别 drop 那几行;P-10 的卡骨架也说「确认后即入库」', () => { + for (const item of ['S0d_p08_master', 'S0d_p10_document']) { + const guide = ASSESSMENT_STEPS.find((s) => s.item === item)?.guide ?? ''; + expect(guide, item).toContain('入了正式表'); + expect(guide, item).not.toContain('已经落草稿层'); + expect(guide, item).toContain('别去 drop'); + } + expect(NARRATIVE).toContain('确认后这批主数据即入库'); + expect(NARRATIVE).toContain('确认后这批资料记录即入库'); + }); + + /* + * S1 的「待确认」行只许来自真写着「未披露」的参数(2026-08-27 用户:「我在第一步已经补充了 + * XL2596S-5.0 使能阈值,当前这一步还要填写一次?」)—— 剧本从前给了一句原话示例 + * 「input_item 如『XL2596S-5.0 使能阈值』」,模型照抄示例、不读现值,把人刚填的 2.5 摆成缺口。 + * 提示词里唯一该出现的原话,是你想听到的那句;示例行一律不引具体料号 + 参数名。 + */ + /* + * 第三次同一病(2026-08-27):措辞改了两遍照样复现 —— 台账里每个参数都有值,模型还是写了一行 + * 「使能阈值 · 原厂未披露」。参数值有没有是台账里的事实,不该由模型在清单里宣布: + * S1 从此**一行参数都不写**,件上摆一张基座现读的「候选参数现值」表;说明书全文不再出现「使能阈值」。 + */ + it('S1 不写参数行(item_key 不许以 unknown_ 开头);说明书全文没有「使能阈值」这个可照抄的名字', () => { + const s1 = ASSESSMENT_STEPS.find((s) => s.item === 'S1_input_confirm')?.guide ?? ''; + expect(s1).toContain('候选参数一行都不写'); + expect(s1).toContain('不许以 unknown_ 开头'); + expect(s1).not.toContain('待确认'); + expect(NARRATIVE).not.toContain('使能阈值'); + for (const step of ASSESSMENT_STEPS) expect(step.guide, step.item).not.toContain('使能阈值'); + const skeleton = NARRATIVE.slice(NARRATIVE.indexOf('⑤ 执行工单 01'), NARRATIVE.indexOf('"standard":"每一条「待补」行')); + expect(skeleton).toContain('候选参数现值'); + expect(skeleton).not.toContain('待确认'); + }); + it('S0 剧本写明都在库时数据工单两项本就不会进「当下可为」;S0d_p08_master 剧本写明 found 的一颗都不建', () => { const s0 = ASSESSMENT_STEPS.find((s) => s.item === 'S0_bind')?.guide ?? ''; expect(s0).toContain('当下可为'); @@ -206,23 +245,47 @@ describe('说明书与剧本的骨架', () => { for (const step of ASSESSMENT_STEPS) expect(step.guide, step.item).not.toMatch(disableCallPattern); }); - it('S0d 拆成两项各自绑自己的 view(P-10 那份件不会永远推不出来),两项都没有 doneWhen', () => { + it('S0d 拆成两项各自绑自己的 view(P-10 那份件不会永远推不出来)', () => { const p08 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master'); const p10 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p10_document'); expect(p08?.view).toBe('p08-master-create'); expect(p10?.view).toBe('p10-document-create'); - expect(p08?.doneWhen).toBeUndefined(); - expect(p10?.doneWhen).toBeUndefined(); }); - it('P-08 交回之后先 finish 再发 P-10 卡,顺序写死进两段 guide(2026-08-27 实测:漏收步会让工作区画面停在 P-08 那两张表,P-10 卡发出后人却看不见资料表、提交闸拦死)', () => { + /* + * **两张数据工单的 doneWhen 只读入库事实**(2026-08-27 晚):引擎只在人答过卡之后求它 —— 值是人在件上填的、 + * 引擎数得出来,不再靠模型记得 finish(剧本写了两处它照样忘,件停在 P-08)。 + */ + it('P-08:要建的每颗料主数据与参数都落了、必填不缺 → 收得掉;少建一颗 / 还缺必填 / 参数行对不上账 → 收不掉', () => { + const ok = { parts_profile_rows: 2, [`${D}_pn_check_exists_status_absent`]: 2, [`${D}_pn_check_exists_status_created`]: 0, parts_profile_missing: 0, parts_parameter_missing: 0, parts_parameter_rows: 14, parts_parameter_distinct_param_name: 7 }; + expect(safeEval(doneOf('S0d_p08_master'), ok)).toBe(true); + // 模型把其中一颗改成 created 也一样算(absent + created 才是要建的料数) + expect(safeEval(doneOf('S0d_p08_master'), { ...ok, [`${D}_pn_check_exists_status_absent`]: 1, [`${D}_pn_check_exists_status_created`]: 1 })).toBe(true); + expect(safeEval(doneOf('S0d_p08_master'), { ...ok, parts_profile_rows: 1, parts_parameter_rows: 7 })).toBe(false); + expect(safeEval(doneOf('S0d_p08_master'), { ...ok, parts_profile_missing: 1 })).toBe(false); + expect(safeEval(doneOf('S0d_p08_master'), { ...ok, parts_parameter_rows: 13 })).toBe(false); + }); + + it('P-10:建过的每颗料都有一行资料记录且必填不缺 → 收得掉;资料少一行 / 骨架还空着 → 收不掉', () => { + const ok = { parts_document_rows: 2, parts_profile_rows: 2, parts_document_missing: 0 }; + expect(safeEval(doneOf('S0d_p10_document'), ok)).toBe(true); + expect(safeEval(doneOf('S0d_p10_document'), { ...ok, parts_document_rows: 1 })).toBe(false); + expect(safeEval(doneOf('S0d_p10_document'), { ...ok, parts_document_missing: 3 })).toBe(false); + }); + + it('S1 的入场哨兵不再等模型写 created 旗:入库事实齐了门就开', () => { + const cond = /[\s\S]*?([\s\S]*?)<\/condition>/.exec(CMMN_XML)?.[1] ?? ''; + expect(cond).not.toContain('sub_eval_pn_check_exists_status_absent = 0'); + expect(cond).toContain('sub_eval_pn_check_exists_status_absent + sub_eval_pn_check_exists_status_created'); + }); + + it('P-08 交回之后的收步不再靠模型记得 finish:剧本写明引擎按入库事实自己收,finish 只是省一拍(2026-08-27 晚:「顺序写死」两处模型照样忘)', () => { const s0d = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master')?.guide ?? ''; expect(s0d).toContain('finish S0d_p08_master'); - expect(s0d).toMatch(/顺序不能反/); - expect(s0d).toContain('p08-master-create'); + expect(s0d).toMatch(/引擎也会自己收/); + expect(s0d).not.toMatch(/顺序不能反/); const s0d10 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p10_document')?.guide ?? ''; - expect(s0d10).toContain('finish S0d_p08_master'); - expect(s0d10).toMatch(/若它还开着|先.*finish/); + expect(s0d10).toMatch(/引擎按入库事实自己收/); }); it('「发卡前清单必须先有行」写在说明书里,不只在剧本里(2026-08-27 实测:模型把 P-10 收步与 S1 发卡压成一拍,那一拍 S1 剧本没摊开,卡发出去时 input_gap 一行都没有,人对着空表无从下手)', () => { @@ -270,7 +333,7 @@ describe('说明书与剧本的骨架', () => { expect(NARRATIVE).toContain('parts.document` 必须先有骨架行'); expect(NARRATIVE).toMatch(/压拍/); // 标了几颗建好,主数据就得有几行 —— 逐颗对账(第一刀写成「≥1 行就算数」太松,三候选那单从缝里过去了) - expect(CMMN_XML).toContain('parts_profile_rows >= sub_eval_pn_check_exists_status_created'); + expect(CMMN_XML).toContain('parts_profile_rows >= sub_eval_pn_check_exists_status_absent + sub_eval_pn_check_exists_status_created'); }); it('评估输入那张卡的**前提**写在说明书的卡骨架上(2026-08-27 e2e:两颗缺库,模型只问只建了第一颗,CN 还 absent 就把 S1 的卡发了出去 —— 发卡的授权面是调人预算,绕得过哨兵)', () => { @@ -295,7 +358,7 @@ describe('说明书与剧本的骨架', () => { const g = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master')?.guide ?? ''; expect(g).toMatch(/参数名一律用中文/); // 措辞写过两遍都没拦住 → 上结构:把「同一套名字」变成基座算得出的数 - expect(CMMN_XML).toContain('parts_parameter_rows = parts_parameter_distinct_param_name * sub_eval_pn_check_exists_status_created'); + expect(CMMN_XML).toContain('parts_parameter_rows = parts_parameter_distinct_param_name * (sub_eval_pn_check_exists_status_absent + sub_eval_pn_check_exists_status_created)'); expect(g).toContain('input_voltage_range'); expect(g).toMatch(/不是同一行/); }); @@ -316,6 +379,7 @@ describe('说明书与剧本的骨架', () => { it('件上三处样式毛病钉死(2026-08-27 用户:表显示不全不能右滑 / 编辑态内部料号只读字与输入框并排 / input 与 select 没对齐)', () => { const html = manifest.assets.viewTemplates.find((v) => v.name === 'p08-master-create')?.html ?? ''; // 两种包装都要能横滑:验收件用目标件的 .tbl-wrap(它的验收版写着 overflow:hidden),2026-08-27 用户在结论审核页撞上 + // 运营侧的看板也在这条纪律里(它六张表都宽) for (const v of manifest.assets.viewTemplates) expect(v.html, v.name).toContain('.jc-tbl-wrap, .tbl-wrap { overflow-x: auto;'); expect(html).toContain('#tbl1.on td > .jc-vcell:not(.ro) { display: none; }'); expect(html).toContain('input.jc-cell, select.jc-cell { height: 32px;'); @@ -347,3 +411,47 @@ describe('说明书与剧本的骨架', () => { expect([...all].sort()).toEqual([...ITEM_IDS.filter((i) => i !== 'T_release')].sort()); }); }); + +/* + * 人工验收清单(2026-08-27 用户定):验收卡上的条目是人核这份工单要走的步骤,不是系统判的判据; + * 每份验收工单固定 —— 差异评估结论审核(S4_review 审核卡)与评审放行与落账确认(T_release 终局验收卡)各一份。 + */ +describe('两张验收工单的人工验收清单', () => { + it('S4_review 与 T_release 各声明一份固定清单:至少 4 条,每条是一句可核对的动作,互不重复', () => { + for (const item of ['S4_review', 'T_release']) { + const list = ASSESSMENT_STEPS.find((s) => s.item === item)?.acceptanceChecklist ?? []; + expect(list.length, item).toBeGreaterThanOrEqual(4); + expect(new Set(list).size, item).toBe(list.length); + for (const line of list) expect(line.length, `${item}: ${line}`).toBeGreaterThan(12); + } + // 别的步骤不发验收卡,不该有清单 + for (const step of ASSESSMENT_STEPS) { + if (step.item === 'S4_review' || step.item === 'T_release') continue; + expect(step.acceptanceChecklist, step.item).toBeUndefined(); + } + }); +}); + +/* + * 评估输入清单每行六列都要写(2026-08-27 实测:模型 11 行全漏了必填列 input_item,件上红带点名「缺输入项」, + * 人把别的格填满也过不了闸;而剧本从前只在括号里提过一次 input_item,逐行的做法里没再说)。 + */ +describe('S1 清单:每行六列都要写', () => { + it('剧本逐行写明 input_item 的人读名与 source_ledger,并让模型发卡前核 _missing = 0', () => { + const s1 = ASSESSMENT_STEPS.find((s) => s.item === 'S1_input_confirm')?.guide ?? ''; + expect(s1).toContain('每一行六列都要写'); + for (const name of ['customer「客户」', 'request「客户需求」', 'original_pn「原 PN」', 'application_scenario「应用场景」', 'bom_position「BOM 位」', 'original_doc_version「原 PN 资料版本」']) { + expect(s1, name).toContain(name); + } + expect(s1).toContain(`${D}_input_gap_missing`); + }); +}); + +/* 放行那一步只 submit,不发工单(2026-08-27 实测:先一张执行单「评审放行与落账确认」,人答完才 submit 又一张验收单) */ +describe('T_release 只 submit', () => { + it('剧本写明不发执行工单 / 决策卡', () => { + const t = ASSESSMENT_STEPS.find((s) => s.item === 'T_release')?.guide ?? ''; + expect(t).toContain('不发执行工单'); + expect(t).toContain('submit'); + }); +}); diff --git a/activities/part-substitution-assessment/src/case-model.ts b/activities/part-substitution-assessment/src/case-model.ts index 19e2438..1178830 100644 --- a/activities/part-substitution-assessment/src/case-model.ts +++ b/activities/part-substitution-assessment/src/case-model.ts @@ -94,7 +94,12 @@ export const CMMN_XML = ` 却只建了第一颗,一行足以让门开着,直到 S2 写关系时才被引用检查拦下。 改成**逐颗对账**:\`parts_profile_rows >= created\` —— 标了几颗建好,就得有几行。 --> - 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 and parts_profile_rows >= sub_eval_pn_check_exists_status_created and parts_document_rows >= sub_eval_pn_check_exists_status_created and parts_parameter_rows = parts_parameter_distinct_param_name * sub_eval_pn_check_exists_status_created + + sub_eval_pn_check_rows = count(candidateInternalPns) + 1 and sub_eval_pn_check_missing = 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 and parts_profile_rows >= sub_eval_pn_check_exists_status_absent + sub_eval_pn_check_exists_status_created and parts_document_rows >= sub_eval_pn_check_exists_status_absent + sub_eval_pn_check_exists_status_created and parts_parameter_rows = parts_parameter_distinct_param_name * (sub_eval_pn_check_exists_status_absent + sub_eval_pn_check_exists_status_created) @@ -130,13 +135,13 @@ export const CMMN_XML = ` - + - + @@ -256,7 +261,7 @@ key 是 {internal_pn, doc_seq:"01"});一行都没有的话人打开是空表、 "ask":"请把这些料号的 datasheet / 承诺函等资料记录补进原厂技术资料库(一份资料一行,可加行)。", "steps":["资料类型、标题、版本、文件链接、资料状态为必填;证据定位写页码或章节,差异评估的证据引用会指向它", "同一料号多份资料就多加几行,序号 01/02… 自己续", - "改完保存,回这张卡确认"], + "改完保存,回这张卡确认 —— 确认后这批资料记录即入库,活动接着往下办"], "standard":"每颗料号至少一行 datasheet 记录,必填列全齐。", "gateLedgers":["parts.document"], "basis":[{"k":"触发原因","v":"按 internal_pn 查 P-10 查无记录"}]}} @@ -275,15 +280,15 @@ key 是 {internal_pn, doc_seq:"01"});一行都没有的话人打开是空表、 {"kind":"human","itemId":"S1_input_confirm","workOrder":"execution","card":{ "gateLedgers":["${D}.input_gap"], "title":"评估输入与资料补齐确认:<原 PN> → 个候选", - "ask":"启动绑定的四项已锁定。请在工作区的件上补齐「待补 / 待确认」行的补充内容,默认值可改可确认。", + "ask":"启动绑定的四项已锁定。请在工作区的件上补齐「待补」行的补充内容,默认值可改可确认。", "steps":["应用场景、BOM 位是启动时未收的:必须补(写在「补充内容」列)", - "原厂未披露的参数(如使能阈值)补不到就写「挂验证任务」或「留 UNKNOWN」—— 补不了也是合法答案,不拦流程", + "每颗料每个参数此刻的值摆在件上的「候选参数现值」表里(只读参考);原厂未披露的参数这里不用补 —— 差异评估会把它判成 UNKNOWN 并挂验证任务", "替代类型 / 规则版本 / 负责人期限三行是系统默认,可改可不改", "加一行 = 补充 AI 没列出的资料项;删一行 = 该项不适用;已绑定的四项不可改", "改完保存,回这张卡提交"], "standard":"每一条「待补」行的补充内容都有;补不了的写明理由。", "basis":[{"k":"已绑定","v":"客户 · 需求 V1 · 原 PN <原 PN> · 候选 颗"}, - {"k":"缺口","v":" 项待补 / 待确认"}]}} + {"k":"缺口","v":" 项待补"}]}} ⑥ 验收工单 01 · 差异评估结论审核(S4_review;决策卡循环,不是终局验收)—— 发之前先在轨迹里找有没有已答过、还没处理的 \`[card-reply]\`: · \`value\` 是 \`"approve"\` → 写 \`${D}.verdict.review_approved_total = 1\`,finish 这一项,别再发卡; @@ -364,7 +369,13 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不 reads: [`${D}.pn_check`, 'candidateInternalPns', 'originalInternalPn', 'datasheets', 'parts.profile', 'parts.parameter', 'parts.document'], view: 'p08-master-create', tools: [], - /* 触达人,不给 doneWhen;人工激活:发卡就算启动 */ + /* + * 人工激活:发卡就算启动。doneWhen 只在**人答过卡之后**才由引擎求(2026-08-27 晚,harness 立此规矩): + * 要建的每一颗(absent + created)主数据与参数都落了、必填不缺 —— 值是人在件上填的,引擎数得出来, + * 不再靠模型记得 finish(剧本写了两处它照样忘,两步同时 Active、件停在 P-08)。名单里还有没建的料时它为假, + * 「再发一张 P-08」那条路照旧走得通。 + */ + doneWhen: `parts_profile_rows > 0 and parts_profile_rows >= ${D}_pn_check_exists_status_absent + ${D}_pn_check_exists_status_created and parts_profile_missing = 0 and parts_parameter_missing = 0 and parts_parameter_rows = parts_parameter_distinct_param_name * parts_profile_rows`, guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走 —— 都在库时入场前提本就不成立,这一项与下一项 S0d_p10_document 都不会进你的「当下可为」。**先分岔,再动作**: 【名单】= \`${D}.pn_check\` 里 **exists_status = "absent" 的每一行**,不管它是原 PN 还是候选 —— **一律建,不问人** @@ -381,13 +392,13 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不 没有 datasheet 就只写 internal_pn 这一列(其余留给人填,**别编**)。 2. 同一拍给每颗料写标准化参数集骨架(表 2,\`parts.parameter\`,一行一参数): **参数名一律用中文,与库里既有料号的那几行同名**(输入电压范围 / 输出电压 / 输出电流 / 开关频率 / - 静态电流 / 工作温度 / 使能阈值…)—— \`param_name\` 是主键的一半,写成 \`input_voltage_range\` + 静态电流 / 工作温度…)—— \`param_name\` 是主键的一半,写成 \`input_voltage_range\` 与写成「输入电压范围」**不是同一行**:原 PN 那边是中文、候选这边是英文,比对时两套名字对不上, 逐条判成 UNKNOWN,结论就废了(2026-08-27 实测:27 条里 14 条 UNKNOWN)。 封装、工艺、生命周期这些**不是参数**,它们在表 1,别往表 2 里塞。 **建几颗料就用同一套名字**:门上有一条对账 —— 参数行数 = 不同参数名数 × 建过的料数, 只有每颗料的名字集合一模一样时才成立;混着写(这颗中文那颗英文、或同一颗写两套)这一步就过不去。有 \`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 留空给人填,别编**)。 @@ -410,14 +421,13 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不 而那颗料从此没人建,后面写替代关系时被引用检查逐颗拦下(2026-08-27 实测,一单连拒四次)。 · **一颗都不剩了** → 才走下面这两步。 -这一拍的动作串**恰好两个,顺序不能反**: -① 先 \`finish S0d_p08_master\` —— **必须先把这一项收掉**。不收掉,引擎挑工作区画面时按 -「当下还没干完的第一步」取 view,S0d_p08_master 只要还 Active,画面就一直停在 -\`p08-master-create\`(P-08 那两张表);下一拍就算发出了 P-10 的卡,人的工作区也看不见 -P-10 的资料表,一格都填不了,提交闸必然拦死。 -② 再照说明书④发 P-10 数据工单 —— 下一项是 S0d_p10_document,同一批料号接着补资料记录。 +这一拍的动作串:① \`finish S0d_p08_master\`(建好的料主数据与参数都已入库、必填不缺时引擎也会自己收这一项 —— +你 finish 一下更省一拍,忘了也不会卡住);② 照说明书④发 P-10 数据工单 —— 下一项是 S0d_p10_document,同一批料号接着补资料记录。 -【这张卡一单只发一次】人交回来之后不许再发;答复里没有值(空 values)也算答过 —— 人在件上填的东西已经落草稿层。`, +【这张卡一单只发一次】人交回来之后不许再发;答复里没有值(空 values)也算答过 —— 人在件上填的东西已随确认**入了正式表** +(轨迹里有一条「数据工单确认入库」的痕点名哪几张表各几行;试跑单不落主数据时那条痕会如实写「只留在活动缓存」)。 +这几颗料从此在库:**别再往 \`parts.profile\` / \`parts.parameter\` 写它们的键**(在库的键会被当场拒),也别去 drop 它们 +(那是本单自己建好的行,撤不掉);值要改由人在件上改。`, }, { item: 'S0d_p10_document', @@ -428,19 +438,21 @@ P-10 的资料表,一格都填不了,提交闸必然拦死。 reads: [`${D}.pn_check`, 'parts.profile', 'parts.document'], view: 'p10-document-create', tools: [], - /* 触达人,不给 doneWhen;人工激活:发卡就算启动 */ - guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走(要走到这一步,S0d_p08_master 必然已经收完);都在库时入场前提本就不成立, -这一项与 S0d_p08_master 都不会进你的「当下可为」。**进到这一项时,S0d_p08_master 应当已经收掉了;若它还开着(Active), -先 \`finish S0d_p08_master\` 再发这张卡** —— 它没收掉,画面会一直停在 P-08 那两张表,人拿到 P-10 的卡也看不见资料表。 + /* 人工激活:发卡就算启动。doneWhen 同 P-08 的道理:人答过卡之后,建过的每颗料都有一行资料记录且必填不缺 → 引擎自己收 */ + doneWhen: `parts_document_rows > 0 and parts_document_rows >= parts_profile_rows and parts_document_missing = 0`, + guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走(要走到这一步,S0d_p08_master 已经收完 —— 引擎按入库事实自己收);都在库时入场前提本就不成立, +这一项与 S0d_p08_master 都不会进你的「当下可为」。 【发 P-10 数据工单】「建」的名单就是 \`${D}.pn_check\` 里仍是 exists_status = "absent" 的那几行(候选总数 ≥2 时被放弃的那些, S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里)。同一拍照说明书④发那张 P-10 数据工单(title 里列全料号,只发一次), 发完这一拍结束(挂起)。 -【P-10 那张确认回来了】把名单里每颗料的 \`${D}.pn_check\` 改成 exists_status = "created"(同 PK 覆盖写),然后 finish 这一项。 -这时 S1_input_confirm 的入场前提(全部料号落定、本单建的主数据不缺必填)自己就成立了 —— 缺必填由提交闸兜着,卡回来时一定是齐的。 +【P-10 那张确认回来了】把名单里每颗料的 \`${D}.pn_check\` 改成 exists_status = "created"(同 PK 覆盖写,记账用),然后 finish 这一项 +(资料记录都入库、必填不缺时引擎也会自己收)。S1_input_confirm 的入场前提认的是**入库事实**(要建的每颗料主数据 / 参数 / 资料都落了且不缺必填), +不等这面旗 —— 缺必填由提交闸兜着,卡回来时一定是齐的。 -【这张卡一单只发一次】人交回来之后不许再发;答复里没有值(空 values)也算答过 —— 人在件上填的东西已经落草稿层。`, +【这张卡一单只发一次】人交回来之后不许再发;答复里没有值(空 values)也算答过 —— 人在件上填的东西已随确认**入了正式表** +(轨迹里有一条「数据工单确认入库」的痕)。这几份资料记录从此在库:**别再往 \`parts.document\` 写它们的键**(会被当场拒),也别去 drop 它们。`, }, { item: 'S1_input_confirm', @@ -453,14 +465,20 @@ S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里) guide: `料号全部落定了,**该 PW 补评估输入了** —— 这是本活动唯一的执行工单,零输入。**先分岔**:【当前这一步】那行引擎事实说 「从没给它发过卡」→ 走【发卡】;说「人已经被问过了」→ 走【收口】。 -【发卡】先把评估输入清单写下来(\`${D}.input_gap\`,一行一项,item_key 用英文短键,input_item 用人读名),一拍连做: -· 已绑定四项(status = "ok",detail 写绑定值,origin = "ai"):item_key = customer / request / original_pn / candidates - (候选多颗就 candidate_1、candidate_2… 一颗一行,detail 写料号与在库状态); +【发卡】先把评估输入清单写下来(\`${D}.input_gap\`,一行一项),一拍连做。 +**每一行六列都要写,一列都不能少**:item_key(英文短键)/ input_item(人读名,就是下面每行「」里那几个字)/ source_ledger(来源台账)/ +status / detail / origin = "ai"。input_item 是必填列 —— 漏了它,件上红带会点名「缺输入项」,人对着一张没有名字的清单什么也补不了, +卡就此交不回来(2026-08-27 实测:11 行全漏了 input_item,人把别的格填满也过不了闸)。 +· 已绑定四项(status = "ok",detail 写绑定值):item_key = customer「客户」(source_ledger = "L-02")/ request「客户需求」(source_ledger = "P-01")/ + original_pn「原 PN」(source_ledger = "P-08")/ 候选多颗就 candidate_1「候选 1」、candidate_2「候选 2」… 一颗一行(source_ledger = "P-08",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.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 版本)。 +· 默认可改三项(status = "default",detail 写默认值,source_ledger = "GOV"):substitution_type「替代类型」= "Pin-to-Pin"、rule_version「规则版本」= "${RULE_VERSION}"、owner「负责人 / 期限」= "按客户线默认 · 1 个工作日"; +· **候选参数一行都不写**:原厂未披露的参数不是这份清单的事 —— 差异评估那一步(S3)会把它判成 UNKNOWN 并挂验证任务; + 件上另有一张只读的「候选参数现值」表(P-08 标准化参数集,一颗料一张,基座从台账现读),人看得见每颗料每个参数此刻的值。 + 这份清单里**不许出现任何参数行**(item_key 不许以 unknown_ 开头)—— 参数值有没有,是台账里的事实,不由你在这里替它宣布 + (2026-08-27 实测三次:人在 P-08 填好的参数被这份清单当成缺口再问一遍); +· 齐备项(status = "ok"):original_doc_version「原 PN 资料版本」(source_ledger = "P-10",detail 写 datasheet 版本)。 +发卡前自己核一遍:\`${D}_input_gap_missing\` 这个读数必须是 0(它只该等 supplement 那一列由人来填;不是 0 就是你漏写了必填列,回去补写再发)。 写完同一拍照说明书⑤发执行工单,发完这一拍结束(挂起)。 【收口】人交回之后只做三件事,然后 finish: @@ -533,7 +551,7 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis 这一步的收步判据里就有这两个数,**不调够,写多少行都收不了步**。 ① 逐条 \`substitution.difference_item\` 同 PK 覆盖写:difference_status / compare_result / risk_level 照第一拍出参**一字不改**; impact_statement(MATCH 写「无」;其余说清对应用 / 验证 / 报价的影响,如「输入上限 32V < 客户 40V,不能上板」); - difference_status = unknown 的行**必须**写 unknown_disposition(「挂验证任务 VAL-<案号数字串>-01:实测使能阈值」或「向原厂补来源」);confidence(0~1)。 + difference_status = unknown 的行**必须**写 unknown_disposition(「挂验证任务 VAL-<案号数字串>-01:实测<参数名>」或「向原厂补来源」);confidence(0~1)。 ② 每个 UNKNOWN 写一行 \`validation.record\`:validation_id = "VAL-<案号数字串>-01/02…"、request_id、validation_internal_pn(候选)、 validation_type = "pin_to_pin"、validation_status = "not_started"、next_validation_action(要测什么、什么条件)、test_condition。 ③ 逐条 \`substitution.relation\` 同 PK 覆盖写:recommendation_level / risk_level 照第二拍出参、recommendation_reason(工具 reason 扩写成人话, @@ -548,6 +566,13 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis item: 'S4_review', /* 中间审核摆成验收工单的样子(机制是可循环的决策卡);由剧本声明,模型抄不抄都一样 */ cardVariant: 'review' as const, + /* 人工验收清单(2026-08-27 用户定):FAE/RI 拿着「差异评估结论审核」这份件要亲自核的步骤,固定 */ + acceptanceChecklist: [ + '逐个候选核对结论汇总:推荐程度、风险级别与它下方明细表的四态分布吻合(有硬约束 MISMATCH 或悬空 UNKNOWN 的候选没有给「推荐」)', + '逐条核对差异明细:每个候选的每个参数都有 MATCH / MISMATCH / UNKNOWN / CONDITIONAL 之一,没有漏项', + '核对 UNKNOWN 项的去向:每个 UNKNOWN 都挂了验证任务或写明补来源动作,没有悬空', + '核对证据引用:每项结论能追溯到 P-08 / P-10 / P-06 的记录或本单数据工单的创建记录;硬约束的 MATCH 都有证据支持', + ], intent: '差异评估结论审核:决策卡循环,通过或驳回 + AI 按意见改台账重发', writes: ['substitution.relation', 'substitution.difference_item', 'validation.record', `${D}.verdict`], reads: ['substitution.relation', 'substitution.difference_item', 'validation.record', `${D}.verdict`], @@ -588,13 +613,23 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis { item: 'T_release', intent: '拟落账内容已备好,提交 ACT-GATE-03 交 FAE/RI 放行;被打回就改台账再交', + /* 人工验收清单(2026-08-27 用户定):FAE/RI 拿着「评审放行与落账确认」这份件要亲自核的步骤,固定 */ + acceptanceChecklist: [ + '核对拟写 P-11 的每条替代关系:推荐等级与差异明细一致(有 UNKNOWN / CONDITIONAL 的候选没有给「推荐」),推荐理由与差异摘要说得通', + '核对分析状态:每条只写分析层(分析状态 = FAE 确认),客户批准状态保持「未提交」,不由本单产生', + '核对规则版本:每条结论都带本次锁定的 GOV 差异规则版本(R-2026.07)', + '核对拟回写 P-01 的五项:风险等级、验证要求、输出链接、下一步动作、当前阶段都填了,且与结论一致', + '核对验证任务:每个 UNKNOWN 对应的验证记录都在,要测什么、什么条件写清楚了', + ], /* 被打回重做时要改的就是这几张 —— 不声明就写不进去 */ writes: ['substitution.relation', 'substitution.difference_item', 'validation.record', 'demand.request', `${D}.verdict`], reads: ['substitution.relation', 'substitution.difference_item', 'validation.record', 'demand.request', `${D}.verdict`], view: 'landing-confirm', tools: [], /* 放行触达世界,必须人按一下 —— 引擎不自动收它 */ - guide: `落账确认那一屏摆着拟写 P-11 主记录与拟回写 P-01。**先看轨迹里有没有「验收被打回重做」**: + guide: `落账确认那一屏摆着拟写 P-11 主记录与拟回写 P-01。**这一步不发执行工单、不发决策卡**:放行卡由 submit 发 +(基座预检后自己发验收卡),发执行工单等于把同一件事问人两遍(2026-08-27 实测:先一张执行单、人答完才 submit 又一张验收单)。 +**先看轨迹里有没有「验收被打回重做」**: 有 → 这一拍不是再提交一遍。照意见改**台账本身**(同 PK 覆盖写,不开新行): · 他说推荐等级与明细不符 → 核对 \`substitution.relation.recommendation_level\` 与该关系明细的四态计数,不符的把 recommendation_reason 改实、把漏挂的验证要求补上; · 他说 P-01 回写不全 → 补 \`demand.request\` 那五项; diff --git a/activities/part-substitution-assessment/src/manifest.ts b/activities/part-substitution-assessment/src/manifest.ts index 39d7e0b..93822f0 100644 --- a/activities/part-substitution-assessment/src/manifest.ts +++ b/activities/part-substitution-assessment/src/manifest.ts @@ -24,6 +24,7 @@ import { activityIdSchema } from '@contracts'; import { ASSESSMENT_SKILLS } from './skills.ts'; import { CMMN_XML, NARRATIVE, ASSESSMENT_PHASES, ASSESSMENT_STEPS } from './case-model.ts'; import { P08_MASTER_CREATE, P10_DOCUMENT_CREATE, EVAL_INPUT_LIST, DIFFERENCE_REVIEW, LANDING_CONFIRM } from './views/report.ts'; +import { LEDGER_BOARD, LEDGER_BOARD_DATA } from './views/ledger-board.ts'; import { ACTIVITY_ID_TEXT, D, TOOL_COMPARE, TOOL_RECOMMEND } from './constants.ts'; export { D, RULE_VERSION, TOOL_COMPARE, TOOL_RECOMMEND } from './constants.ts'; @@ -89,7 +90,7 @@ export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'ACT-WORK-05 · 差异评估', - packageVersion: '1.4.7', + packageVersion: '1.6.2', category: '需求分析与方案匹配', sdkVersion: '0.1.0-m5', checksum: 'dev-checksum-part-substitution-assessment', @@ -109,20 +110,57 @@ export const manifest: ActivityManifest = { data: { type: 'object', required: ['customerCode', 'requestDescription', 'originalInternalPn', 'candidateInternalPns'], + /* + * 运营端「启动活动」表单照这几个字段渲染(2026-08-27,`docs/API文档-活动模块.md` §4): + * `x-control` 定控件(combo = 选择 + 新增,textarea = 多行),`x-options` 指候选从哪张正式台账现读,`x-order` 定表单次序 + * (清单存在 jsonb 里,书写顺序到不了运营端 —— PG 会按键长重排), + * `description` 是控件下那句辅助说明。台账里没有的值**照样启动**,启动后由 S0 的存在性核对派数据工单。 + * datasheets 是文件,本期不上启动表单(在数据工单里传)。 + */ properties: { - customerCode: { type: 'string', title: '客户编码(L-02 客户主台账)', examples: ['CU-0142'] }, - customerName: { type: 'string', title: '客户名称(仅客户不在库时填,启动后先建档)', examples: ['深圳恒星电源有限公司'] }, + customerCode: { + type: 'string', + title: '客户', + description: '台账里有就直接选;是新客户就自己填客户编码,启动后派数据录入卡建档。', + examples: ['CU-0142'], + 'x-control': 'combo', + 'x-options': { ledger: 'customer.profile', value: 'customer_code', label: 'name' }, + 'x-order': 1, + }, + customerName: { + type: 'string', + title: '客户名称(仅客户不在库时填)', + description: '新客户先把名字摆上,启动后建档时让人核。', + examples: ['深圳恒星电源有限公司'], + 'x-order': 2, + }, requestDescription: { type: 'string', - title: '客户需求(原始描述 —— 据此新建 P-01 需求主记录 · 版本 V1)', + title: '客户需求', + description: '保留原始需求语义,据此新建 P-01 需求主记录(版本 V1)。', examples: ['工业电源项目在用 LM2596S-5.0(TI,12V→5V),原厂 PCN 转 NRND,客户要求验证替代料'], + 'x-control': 'textarea', + 'x-order': 3, + }, + originalInternalPn: { + type: 'string', + title: '原内部料号', + description: '差异评估基准;库里没有该 PN 也可启动,启动后派数据工单补参数。', + examples: ['LM2596S-5.0'], + 'x-control': 'combo', + 'x-options': { ledger: 'parts.profile', value: 'internal_pn', label: 'internal_pn' }, + 'x-order': 4, }, - originalInternalPn: { type: 'string', title: '原内部料号(P-08)', examples: ['LM2596S-5.0'] }, candidateInternalPns: { type: 'array', - title: '候选内部料号(数组,≥1;不在库的启动后经数据工单创建)', + title: '候选内部料号', + description: '被评估物料;库里有就直接选,没有就自己填料号,启动后经数据工单创建。', items: { type: 'string' }, examples: [['XL2596S-5.0', 'LM2596S-ADJ']], + /* 运营端启动表单只选一颗(2026-08-27 用户定):摆成与原料号同款的下拉;提交的单个料号由后端包成一元数组,活动契约不变 */ + 'x-control': 'combo', + 'x-options': { ledger: 'parts.profile', value: 'internal_pn', label: 'internal_pn' }, + 'x-order': 5, }, datasheets: { type: 'array', @@ -257,7 +295,7 @@ export const manifest: ActivityManifest = { skills: ASSESSMENT_SKILLS.map((s) => s.id), models: ['deepseek-v4-flash'], roles: ['FAE/RI'], - budget: { steps: 90, tokens: 600_000, activeMinutes: 120, humanCalls: 16 }, + budget: { steps: 90, tokens: 2_000_000, activeMinutes: 120, humanCalls: 16 }, }, }, @@ -561,6 +599,9 @@ col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true { name: 'eval-input-list', html: EVAL_INPUT_LIST }, { name: 'difference-review', html: DIFFERENCE_REVIEW }, { name: 'landing-confirm', html: LANDING_CONFIRM }, + /* 运营中心 L2 的沙箱视图(2026-08-27,用户定替换掉全流程分支图):关键台账数据看板 —— 最近一次已放行 + 实例的落账快照,行由基座按 `data` 声明从正式表圈;无脚本,报高脚本由服务端出口注入 */ + { name: 'activity-ledger', html: LEDGER_BOARD, data: LEDGER_BOARD_DATA }, ], cardSchemas: {}, distillHooks: [{ on: 'terminal', prompt: '归纳本次差异评估:哪些候选、各自推荐等级与关键依据、UNKNOWN 挂了什么验证任务、审核是否被驳回过(为什么)、数据工单建了哪些主数据。' }], diff --git a/activities/part-substitution-assessment/src/views/badge.ts b/activities/part-substitution-assessment/src/views/badge.ts new file mode 100644 index 0000000..ee990b3 --- /dev/null +++ b/activities/part-substitution-assessment/src/views/badge.ts @@ -0,0 +1,18 @@ +/** + * 徽标别名的生成器(与 `report.ts` 里那一段同一套逻辑;report.ts 另有一份 —— 并成一处记在 debt)。 + * + * **2026-08-27 实测事故**:手写的 `.jc-tag.v-pin_to_pin::after{…}` 永远匹配不上,件上显示「待定」, + * 而台账里 `substitution_type` 明明是 `pin_to_pin`。`data-class-of` 投影出的 class 走 `view-render.ts` 的 + * `classToken`(只留 `[a-z0-9-]`,**吃掉下划线**,封顶 24 字),所以选择器一律从 tally 值**算**出来。 + */ +/** 必须与 `classToken` 一字不差:小写、只留 `[a-z0-9-]`、封顶 24 字。 */ +export const vtok = (v: string): string => v.toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 24); +/** `[值, 人读名, 可选颜色声明]`:值与文案成对写在一处 */ +export type BadgeSpec = readonly [value: string, label: string, color?: string]; +export const badgeCss = (selector: string, specs: readonly BadgeSpec[]): string => + specs + .map(([v, label, color]) => { + const cls = `${selector}.v-${vtok(v)}`; + return color === undefined ? `${cls}::after { content: "${label}"; }` : `${cls}::after { content: "${label}"; }\n${cls} { ${color} }`; + }) + .join('\n'); diff --git a/activities/part-substitution-assessment/src/views/ledger-board.ts b/activities/part-substitution-assessment/src/views/ledger-board.ts new file mode 100644 index 0000000..ff52b46 --- /dev/null +++ b/activities/part-substitution-assessment/src/views/ledger-board.ts @@ -0,0 +1,517 @@ +/** + * 运营中心 L2 沙箱视图 —— ACT-WORK-05「关键台账数据」看板(2026-08-27)。 + * + * 版式与样式逐字节搬自 `docs/ss02-act-05/ACT-WORK-05-关键台账数据.html`(只改两处:`.tbl-wrap` 放开横滑; + * 徽标文案由 `data-class-of` 投影 + 生成式 `::after` 出,不再写死)。溯源口径照 `…-数据溯源.md`: + * 页头 / P-01 一条 / P-08 逐颗 + 参数瓷贴(一格一列,不叠灰字次列 —— 2026-08-27 用户定)/ P-11 主记录 + 差异明细 / P-10(同样一格一列)/ P-06(本单 + 同料号历史)。 + * + * **它摆的是最近一次已放行实例的落账快照**(用户定):行由基座按 `LEDGER_BOARD_DATA` 的声明从**正式表**圈出 —— + * 每个 `data-rows` 键都是声明里的一个 `as`,不是整表;`.<列>` 是那一集合首行的标量,`_rows` / + * `_<列>_<值>` 是基座数出来的读数(`apps/api/src/app/activity-ops/view-scope.ts`)。模版仍**不算不判**: + * MATCH 行淡显、放弃的候选、三态横幅,全是 `data-class-of` 投出的 class 交样式表。 + * 无脚本、无外链(过安装静态审);报高脚本由服务端出口注入。 + */ +import type { ViewData } from '@contracts'; +import { badgeCss } from './badge.ts'; +import { D } from '../constants.ts'; + +/** 案号 `W05-<开案时刻 14 位>`(与说明书让模型铸 `eval_ref` 的写法同源);行集按案号 → 核对表 → 料号 / 需求逐表圈 */ +export const LEDGER_BOARD_DATA: ViewData = { + case: { ledger: `${D}.verdict`, key: 'eval_ref', prefix: 'W05-' }, + rows: [ + { as: `${D}.verdict`, ledger: `${D}.verdict`, by: { column: 'eval_ref', from: 'case' } }, + /* 原 PN 排第一(original > candidate),下游按料号圈的表都随这个顺序 */ + { as: `${D}.pn_check`, ledger: `${D}.pn_check`, by: { column: 'eval_ref', from: 'case' }, orderBy: [{ field: 'role', dir: 'desc' }, { field: 'internal_pn', dir: 'asc' }] }, + { as: 'demand.request', ledger: 'demand.request', by: { column: 'request_id', from: `${D}.verdict.request_id` } }, + { as: 'customer.profile', ledger: 'customer.profile', by: { column: 'customer_code', from: 'demand.request.customer_code' } }, + { as: 'parts.profile', ledger: 'parts.profile', by: { column: 'internal_pn', from: `${D}.pn_check.internal_pn` } }, + { as: 'parts.parameter', ledger: 'parts.parameter', by: { column: 'internal_pn', from: `${D}.pn_check.internal_pn` }, orderBy: [{ field: 'param_name', dir: 'asc' }] }, + { as: 'parts.document', ledger: 'parts.document', by: { column: 'internal_pn', from: `${D}.pn_check.internal_pn` }, orderBy: [{ field: 'doc_seq', dir: 'asc' }] }, + { as: 'substitution.relation', ledger: 'substitution.relation', by: { column: 'eval_ref', from: 'case' }, orderBy: [{ field: 'substitution_relation_id', dir: 'asc' }] }, + { + as: 'substitution.difference_item', + ledger: 'substitution.difference_item', + by: { column: 'substitution_relation_id', from: 'substitution.relation.substitution_relation_id' }, + orderBy: [{ field: 'difference_item_id', dir: 'asc' }], + }, + { as: 'validation.record', ledger: 'validation.record', by: { column: 'request_id', from: `${D}.verdict.request_id` }, orderBy: [{ field: 'validation_id', dir: 'asc' }] }, + /* 同料号在别单 / 别处留下的验证记录:本次评估的历史证据引用(目标件 P-06 里 VAL-2026-0040 / 0032 那两行的位置) */ + { + as: 'validation.history', + ledger: 'validation.record', + by: { column: 'validation_internal_pn', from: `${D}.pn_check.internal_pn` }, + not: { column: 'request_id', from: `${D}.verdict.request_id` }, + orderBy: [{ field: 'validation_id', dir: 'desc' }], + }, + ], +}; + +/* ==================== 目标件样式(逐字节搬;两处改动标了 [改]) ==================== */ +const TARGET_CSS = ` +:root{ + --ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e; + --line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb; + --brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff; + --data:#3370ff; --data-soft:#e1eaff; --data-line:#c3d6ff; + --human:#d97706; --human-soft:#fdf1dc; --human-line:#f0d8a8; + --ledger:#16a34a; --ledger-soft:#e6f6ec; --ledger-line:#b7e5c6; + --ok:#278717; --ok-soft:#e8f8e4; --danger:#d83931; --danger-soft:#feeae9; + --amber:#8f6a12; --amber-soft:#fdf3d7; --amber-line:#ecd9a0; + --mono:"SF Mono",Menlo,Consolas,monospace; +} +*{box-sizing:border-box} +html,body{margin:0;padding:0} +body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:#fff;color:var(--ink);font-size:14px;line-height:1.7;-webkit-font-smoothing:antialiased} +.page{max-width:none;margin:0;padding:0} + +/* 头部 */ +.mast{background:#fff;border:1px solid var(--line-soft);border-radius:14px;padding:22px 26px;position:relative;overflow:hidden} +.mast::before{content:"";position:absolute;inset:0 0 auto 0;height:4px;background:linear-gradient(90deg,var(--brand) 0%,var(--brand-deep) 100%)} +.mast .kick{font-size:11px;letter-spacing:.12em;color:var(--ink-4);font-weight:800} +.mast h1{margin:4px 0 6px;font-size:23px;font-weight:800;line-height:1.3} +.mast h1 .code{font-family:var(--mono);font-size:14px;font-weight:700;color:var(--brand-deep);background:var(--brand-wash);border:1px solid var(--brand-soft);border-radius:8px;padding:3px 10px;margin-right:10px;vertical-align:3px} +.mast .sub{font-size:13px;color:var(--ink-3)} +.mast .sub b{color:var(--ink-2)} + +/* 数据来源图例 */ +.legend{display:flex;gap:10px;flex-wrap:wrap;margin:12px 0 0;font-size:12px;color:var(--ink-3)} +.legend .li{display:inline-flex;align-items:center;gap:6px;background:#fff;border:1px solid var(--line-soft);border-radius:999px;padding:3px 12px} +.dot{width:8px;height:8px;border-radius:50%;flex:none} +.dot.full{background:var(--brand)} +.dot.half{background:var(--amber)} + +/* 分层图例 */ +.layers{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin:14px 0 18px} +.layer{background:#fff;border:1px solid var(--line-soft);border-radius:11px;padding:11px 14px;font-size:12px;color:var(--ink-2);border-top:3px solid var(--brand)} +.layer b{display:block;font-size:12.5px;color:var(--ink);margin-bottom:2px} +.layer .lg{font-family:var(--mono);font-weight:800;color:var(--brand-deep)} +.layer.core-out{border-top-color:var(--ledger)} .layer.core-out .lg{color:var(--ledger)} +.layer.evid{border-top-color:var(--human)} .layer.evid .lg{color:var(--human)} +.layer.loop{border-top-color:var(--amber)} .layer.loop .lg{color:var(--amber)} +.layer.ctx{border-top-color:var(--ink-4)} .layer.ctx .lg{color:var(--ink-3)} + +/* 区块 */ +section{background:#fff;border:1px solid var(--line-soft);border-radius:14px;padding:18px 22px;margin-bottom:16px} +.sec-head{display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap} +.sec-head h2{margin:0;font-size:15.5px;font-weight:800} +.sec-head h2 .lid{font-family:var(--mono);font-size:12px;color:var(--brand-deep);background:var(--brand-wash);border:1px solid var(--brand-soft);border-radius:6px;padding:1px 8px;margin-right:8px} +.ltag{font-size:10.5px;font-weight:800;padding:2px 9px;border-radius:999px;border:1px solid transparent;white-space:nowrap} +.ltag.in{color:var(--brand-deep);background:var(--brand-wash);border-color:var(--brand-soft)} +.ltag.out{color:var(--ledger);background:var(--ledger-soft);border-color:var(--ledger-line)} +.ltag.evid{color:var(--human);background:var(--human-soft);border-color:var(--human-line)} +.ltag.loop{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)} +.ltag.ctx{color:var(--ink-3);background:var(--soft);border-color:var(--line)} +.sec-head .why{font-size:12px;color:var(--ink-4)} +.sec-head .cnt{margin-left:auto;font-size:11.5px;color:var(--ink-4);font-family:var(--mono)} + +/* 表格 —— [改] 目标件写 overflow:hidden,列一多右边被裁且滑不动;本仓 2026-08-23 定:表宽超出就得能横滑 */ +.tbl-wrap{border:1px solid var(--line-soft);border-radius:10px;overflow-x:auto;overflow-y:hidden} +.jc-tbl-wrap, .tbl-wrap { overflow-x: auto; overflow-y: hidden; } +table{width:100%;border-collapse:collapse;font-size:12.5px} +th{text-align:left;background:var(--soft);font-size:11px;letter-spacing:.05em;font-weight:800;color:var(--ink-3);padding:9px 12px;border-bottom:1px solid var(--line);white-space:nowrap} +td{padding:9px 12px;border-bottom:1px solid var(--line-soft);vertical-align:top} +tr:last-child td{border-bottom:0} +tbody tr{transition:background .15s} +tbody tr:hover{background:var(--brand-wash)} +.mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)} +.pn{font-weight:700} + +/* 状态 pill */ +.st{display:inline-block;padding:1px 9px;border-radius:999px;font-size:11px;font-weight:800;white-space:nowrap;border:1px solid transparent} +.st.ok{color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4} +.st.miss{color:var(--danger);background:var(--danger-soft);border-color:#f3b9b5} +.st.unknown{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)} +.st.cond{color:var(--brand-deep);background:var(--brand-wash);border-color:var(--brand-soft)} +.st.gray{color:var(--ink-3);background:var(--soft);border-color:var(--line)} +.st.warn{color:var(--human);background:var(--human-soft);border-color:var(--human-line)} + +/* P-01 单记录 KV 卡 */ +.kv{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line-soft);border:1px solid var(--line-soft);border-radius:10px;overflow:hidden} +.kv .cell{background:#fff;padding:10px 14px} +.kv .k{font-size:11px;font-weight:800;color:var(--ink-4);margin-bottom:2px} +.kv .v{font-size:12.5px;color:var(--ink);line-height:1.5} +.kv .span2{grid-column:span 2} + +/* 参数集摘要 chips */ +.pchips{display:flex;flex-wrap:wrap;gap:4px} +.pchips span{font-size:11px;font-family:var(--mono);background:var(--soft);border:1px solid var(--line-soft);border-radius:6px;padding:1px 7px;color:var(--ink-2);white-space:nowrap} + +/* 次要台账条 */ +.minor{display:flex;gap:10px;flex-wrap:wrap} +.minor .m{flex:1;min-width:220px;border:1px solid var(--line-soft);border-radius:10px;padding:11px 14px;font-size:12px;color:var(--ink-2)} +.minor .m b{display:block;font-size:12.5px;color:var(--ink);margin-bottom:3px} +.minor .m .lid{font-family:var(--mono);color:var(--ink-4);font-weight:700;margin-right:6px} + +footer{margin-top:18px;font-size:11.5px;color:var(--ink-4);display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px} +@media(max-width:900px){.layers{grid-template-columns:repeat(2,1fr)}.kv{grid-template-columns:1fr 1fr}.kv .span2{grid-column:span 2}} +`; + +/* ==================== 绑定层:徽标别名 / 三态横幅 / 淡显 —— 只取值,不算不判 ==================== */ +const OK = 'color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4;'; +const MISS = 'color:var(--danger);background:var(--danger-soft);border-color:#f3b9b5;'; +const UNKNOWN = 'color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line);'; +const COND = 'color:var(--brand-deep);background:var(--brand-wash);border-color:var(--brand-soft);'; +const GRAY = 'color:var(--ink-3);background:var(--soft);border-color:var(--line);'; +const WARN = 'color:var(--human);background:var(--human-soft);border-color:var(--human-line);'; + +const BIND_CSS = ` +/* 徽标:值域各归各族(同一个词在不同列意思不同,如 pending 在资料状态是待生效、在需求状态是待处理), + 文案与颜色由 data-class-of 投出的 v- 决定;没投出 class 的格子摆「—」,客户批准那格摆「未提交」 */ +.st::after{content:"—"} +.st.ap::after{content:"未提交"} +${badgeCss('.st.lc', [ + ['active', 'Active', OK], + ['nrnd', 'NRND', MISS], + ['eol', 'EOL', MISS], + ['discontinued', '停产', MISS], + ['risk', '风险', WARN], +])} +${badgeCss('.st.rec', [ + ['recommended', '推荐', OK], + ['conditional', '条件推荐', WARN], + ['alternate', '备选', COND], + ['not_recommended', '不推荐', MISS], +])} +${badgeCss('.st.an', [ + ['ai_draft', 'AI 草稿', GRAY], + ['fae_confirmed', 'FAE确认', COND], + ['pending_customer', '待客户验证', WARN], + ['customer_approved', '客户已批准', OK], + ['rejected', '不批准', MISS], + ['void', '作废', GRAY], +])} +${badgeCss('.st.rk', [ + ['low', '低', OK], + ['medium', '中', WARN], + ['high', '高', MISS], + ['critical', '危急', MISS], +])} +${badgeCss('.st.dst', [ + ['match', 'MATCH', OK], + ['mismatch', 'MISMATCH', MISS], + ['unknown', 'UNKNOWN', UNKNOWN], + ['conditional', 'CONDITIONAL', COND], +])} +${badgeCss('.st.vs', [ + ['not_started', '未开始', GRAY], + ['in_progress', '进行中', COND], + ['passed', '通过', OK], + ['failed', '未通过', MISS], + ['conditional', '条件通过', WARN], + ['pending_evidence', '待证据', UNKNOWN], +])} +${badgeCss('.st.ds', [ + ['valid', '有效', OK], + ['expired', '过期', MISS], + ['pending', '待生效', WARN], + ['void', '作废', GRAY], +])} +${badgeCss('.st.rq', [ + ['pending', '待处理', GRAY], + ['responded', '已响应', COND], + ['won', '已成交', OK], + ['abandoned', '已放弃', MISS], + ['closed', '已关闭', GRAY], +])} +${badgeCss('.st.stg', [ + ['assessing', '评估中', GRAY], + ['gate03', '提交 ACT-GATE-03', OK], +])} +${badgeCss('.st.ex', [ + ['found', '在库直读', COND], + ['created', '本单建档', OK], + ['absent', '缺库', MISS], + ['abandoned', '已放弃', GRAY], +])} +${badgeCss('.st.rl', [ + ['original', '原 PN', COND], + ['candidate', '候选', GRAY], +])} +/* 纯文字的枚举列:只补文案 */ +.enumtxt::after{content:"—"} +${badgeCss('.enumtxt.ty', [ + ['pin_to_pin', 'Pin-to-Pin'], + ['functional', '功能替代'], + ['parametric', '参数替代'], + ['other', '其他'], +])} +${badgeCss('.enumtxt.con', [ + ['hard', '硬约束'], + ['soft', '软约束'], +])} +${badgeCss('.enumtxt.vt', [ + ['pin_to_pin', 'Pin-to-Pin'], + ['functional', '功能'], + ['reliability', '可靠性'], + ['certification', '认证'], + ['retest', '复测'], +])} +${badgeCss('.enumtxt.dt', [ + ['datasheet', 'Datasheet'], + ['app_note', '应用笔记'], + ['reference_design', '参考设计'], + ['white_paper', '白皮书'], + ['sample_doc', '样品资料'], + ['commitment_letter', '承诺函'], + ['other', '其他'], +])} + +/* 页头候选料号:一行内逐个摆,用顿号连;一个都没有就摆「—」 */ +.cands b+b::before{content:"、";font-weight:400;color:var(--ink-3)} +.cands:empty::after{content:"—"} +/* 参数瓷贴:参数名淡、值粗、单位紧跟 */ +.pchips i{font-style:normal;color:var(--ink-4);margin-right:3px} +.pchips b{font-weight:700} +.pchips em{font-style:normal;margin-left:1px} +.pchips:empty::after{content:"参数集未登记";font-size:11px;color:var(--ink-4)} +/* 料号格里的两枚小徽(角色 / 在库状态) */ +.pnrow{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px} +.pnrow .st{font-size:10px;padding:0 7px} +/* 差异明细:MATCH 行淡显(不藏:那也是比对过的事实),非 MATCH 才是要看的 */ +tr.v-match td{opacity:.55} +/* 已放弃的候选:只在有 abandoned 行时露面,别的核对行不显 */ +.aband{display:none;font-size:12px;color:var(--ink-3);margin:8px 0 0} +.aband i{font-style:normal;font-family:var(--mono);color:var(--ink-2)} +.aband i:not(.v-abandoned){display:none} +.aband i+i.v-abandoned::before{content:"、"} +.aband:has(i.v-abandoned){display:block} +/* P-06 历史行:首格挂一枚「历史」灰签 */ +tbody.hist td:first-child::after{content:"历史";display:inline-block;margin-left:6px;font-size:10px;font-weight:800;padding:0 6px;border-radius:999px;color:var(--ink-3);background:var(--soft);border:1px solid var(--line);font-family:Inter,ui-sans-serif,system-ui,sans-serif} +td .sub{display:block;font-size:11px;color:var(--ink-3);font-weight:400} +td .sub:empty{display:none} +/* 三态横幅:none = 本活动还没有已放行实例;missing = 有实例但案号在正式表里查无 */ +.notice{display:none;align-items:flex-start;gap:8px;margin:0 0 16px;padding:12px 16px;border-radius:12px;font-size:13px;line-height:1.6} +.notice.none{background:var(--soft);border:1px solid var(--line);color:var(--ink-2)} +.notice.missing{background:var(--amber-soft);border:1px solid var(--amber-line);color:var(--amber)} +.page.v-none .notice.none{display:flex} +.page.v-missing .notice.missing{display:flex} +.page.v-none .layers,.page.v-none section,.page.v-none .mast .sub.case,.page.v-none .mast .sub.verdict{display:none} +.page.v-missing section,.page.v-missing .mast .sub.verdict{display:none} +`; + +const th = (...cols: string[]): string => `${cols.map((c) => `${c}`).join('')}`; + +export const LEDGER_BOARD = ` + + + + +ACT-WORK-05 差异评估 · 关键台账数据 · SS02 需求分析与方案匹配 + + + +
+ +
+
SS02 需求分析与方案匹配 · 活动台账视图
+

ACT-WORK-05关键台账数据

+

视角:本活动最近一次已放行实例 ( 开案 · 放行 · 负责人 )的落账快照 · 案号 · 关联需求 (客户 )· 评估基准 ()→ 候选 · 规则版本

+

评估结论:

+
+ 正式台账直读 —— 放行落地后的事实,按案号圈出这一单的行 + 同料号的历史验证记录(别单 / 线下留下的,作本次评估的证据引用) +
+
+ +
本活动还没有已放行的实例 —— 有一单放行后,这里摆它的关键台账落账快照。
+
实例 已放行,但案号 在正式表里查无 —— 多半是起单时关了「落真库」(主数据只留在活动缓存),这一单没有可摆的落账数据。
+ + +
+
P-08 芯片主数据核心输入 · 评估原材料,查无即硬阻塞
+
P-11 替代批准关系库核心产出 · 结论与明细的落账地
+
P-10 原厂技术资料库证据链 · 每项结论的版本化出处
+
P-06 样品验证记录闭环去向 · UNKNOWN 的实测收口
+
P-01 客户需求主台账上下文 · 需求源头与回写对象
+
+ + +
+
+

P-01客户需求主台账

+ 上下文 · R+W + 启动时新建需求;结束时回写风险 / 验证要求 / 输出链接 / 下一步 / 当前阶段 + 正式表 · 条 +
+
+
需求编号
+
客户
+
需求版本
+
需求状态
+
需求描述
+
应用场景
+
BOM 位
+
原内部料号
+
候选内部料号
+
风险等级(回写)
+
验证要求(回写)
+
当前阶段
+
标准输出链接
+
下一步动作
+
+
+ + +
+
+

P-08芯片主数据台账

+ 核心输入 · R(查无 → 数据工单创建) + 原 PN 与候选 PN 的物料基准 + 标准化参数集(子表 P-08 参数),逐项比对的原材料 + 正式表 · 条(本活动相关) +
+
+ + ${th('内部料号', '品牌', '规格描述', '封装', '生命周期', 'Datasheet 版本', '标准化参数集', '适配场景')} + + + + + + + + + + + + +
+
+

本轮放弃的候选(未建档、未评估):

+
+ + +
+
+

P-11替代选型与批准关系库 · 替代关系主记录

+ 核心产出 · R+W + 每个候选一条关系记录;客户批准状态为只读边界,本活动不写 + 正式表 · 条 +
+
+ + ${th('替代关系 ID', '原 → 候选', '替代类型', '推荐等级', '分析状态', '风险', '差异摘要', '验证要求 / 回链', '规则版本', '客户批准(只读)')} + + + + + + + + + + + + + + +
→
+
+ +
+

P-11差异明细子表(由双方 P-08 标准化参数集逐项比对得出;MATCH 项淡显)

+ 条 · MATCH · MISMATCH · UNKNOWN · CONDITIONAL +
+
+ + ${th('所属关系', '参数项', '原值 → 候选值', '约束', '结论', '风险', '证据引用', '影响说明')} + + + + + + + + + + + + +
→
+
+
+ + +
+
+

P-10原厂技术资料库

+ 证据链 · R(查无 → 数据工单创建) + 每项结论的 datasheet 出处,版本回链 + 正式表 · 条(本活动相关料号) +
+
+ + ${th('关联料号', '品牌', '资料类型', '资料文件', '版本', '状态')} + + + + + + + + + + +
·
+
+
+ + +
+
+

P-06样品交付与芯片验证记录台账

+ 闭环去向 · R + 条件写(不可创建,只能实测产生) + UNKNOWN / CONDITIONAL 的收口处;验证完成后回链 P-11 + 本单挂的 · 条 · 同料号历史 · 条 +
+
+ + ${th('验证 ID', '关联需求', '验证料号', '验证类型', '测试条件', '结果 / 状态', '问题与风险', '后续动作')} + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+
+

次要台账(只读引用,不进主看板)

+
+
+
P-09客户技术与机会档案提供 BOM 位与客户技术栈背景;缺口由执行工单补齐。
+
EXT-02外部来源清单登记本次评估用到的可信外部来源(原厂官网 datasheet 等)。
+
GOV评估规则库四态判定口径;本次锁定版本 ,随结论一并落账。
+
+
+ +
+ SS02 · ACT-WORK-05 差异评估 · 关键台账数据视图 + 数据来源:正式台账(parts / demand / substitution / validation / sub_eval)· 实例 · 案号 +
+ +
+ + +`; diff --git a/activities/part-substitution-assessment/src/views/report.ts b/activities/part-substitution-assessment/src/views/report.ts index 31e2ded..f743047 100644 --- a/activities/part-substitution-assessment/src/views/report.ts +++ b/activities/part-substitution-assessment/src/views/report.ts @@ -140,6 +140,14 @@ ${badgeCss('.ct', [ /* ==================== 执行工单(评估输入清单):徽标别名 ==================== */ const BIND_EXEC = ` +/* 候选参数现值(只读参考块):一颗料一张,复用 .jc-tbl-wrap;块头摆料号 / 角色 / 在库状态 */ +.prefs { display: block; } +.pref { border: 1px solid var(--line-soft); border-radius: 10px; overflow: hidden; margin-bottom: 12px; } +.pref:last-of-type { margin-bottom: 0; } +.pref-head { display: flex; align-items: center; gap: 10px; padding: 9px 14px; background: var(--soft); border-bottom: 1px solid var(--line); } +.pref-head b { font-size: 13.5px; } +.pref .jc-tbl-wrap { border: 0; border-radius: 0; } + /* 存在性核对的两列(角色 / 在库状态)与评估输入清单的状态列共用目标件自己的 .st 徽标基座 —— 取值域互不重叠(found/absent/created/abandoned 与 gap/default/ok 没有一个词撞车)。 四档配色**原样照抄目标件自己的 .st.bound / .st.ok / .st.miss / .st.def 那四条声明**,只换成 v- 版。 */ @@ -181,14 +189,21 @@ ${badgeCss('.st.dst', [ ['match', 'MATCH', OK], ['mismatch', 'MISMATCH', DANGER], ['unknown', 'UNKNOWN', AMBER], - ['conditional', 'CONDITIONAL', 'color: var(--ledger); background: var(--ledger-soft); border-color: var(--ledger-line);'], + // 目标件 2026-08-27 改版:CONDITIONAL 从题头红改成品牌蓝(.st.cond),原样照抄 + ['conditional', 'CONDITIONAL', 'color: var(--brand-deep); background: var(--brand-wash); border-color: var(--brand-soft);'], ])} -${badgeCss('.st.rec', [ +/* 推荐程度 pill —— 目标件 .rec.yes / .rec.cond / .rec.no 三条声明原样照抄;台账是四档(备选借条件推荐那档琥珀) */ +${badgeCss('.rec', [ ['recommended', '推荐', OK], - ['conditional', '条件推荐', 'color: var(--ledger); background: var(--ledger-soft); border-color: var(--ledger-line);'], - ['alternate', '备选', AMBER], + ['conditional', '有条件推荐', 'color: var(--amber); background: var(--amber-soft); border-color: var(--amber-line);'], + ['alternate', '备选', 'color: var(--amber); background: var(--amber-soft); border-color: var(--amber-line);'], ['not_recommended', '不推荐', DANGER], ])} +/* 候选块序号(目标件 .cand-idx 写死 1/2)—— 交给 CSS 计数器;块头的风险级别前缀「风险 」也在目标件里 */ +.card-bd { counter-reset: cand; } +.cand { counter-increment: cand; } +.cand-idx::before { content: counter(cand); } +.cand-con .risk::before { content: "风险 "; } /* * 风险等级(原样照抄目标件 .risk.low/.mid/.hi 三条声明)——加 critical 一档:这一屏「各候选 * 推荐等级」摘要读的是 substitution.relation.risk_level(声明了 low/medium/high/critical @@ -196,7 +211,7 @@ ${badgeCss('.st.rec', [ */ ${badgeCss('.risk', [ ['low', '低', 'color: var(--ok);'], - ['medium', '中', 'color: #8f6a12;'], + ['medium', '中', 'color: var(--amber, #8f6a12);'], ['high', '高', 'color: var(--danger);'], ['critical', '危急', 'color: var(--danger); font-weight: 900;'], ])} @@ -246,7 +261,7 @@ ${badgeCss('.st', [ /* 风险等级比验收 01 多一档 critical(P-11 主记录的 risk_level 声明了它,差异明细没有) */ ${badgeCss('.risk', [ ['low', '低', 'color: var(--ok);'], - ['medium', '中', 'color: #8f6a12;'], + ['medium', '中', 'color: var(--amber, #8f6a12);'], ['high', '高', 'color: var(--danger);'], ['critical', '危急', 'color: var(--danger); font-weight: 900;'], ])} @@ -496,6 +511,25 @@ const PN_CHECK_TABLE = `
`; +/** 候选参数现值 —— **只读参考,基座从台账现读**(2026-08-27,用户三次指出「库里明明有值,清单还要填一遍」): + * 从前「待确认」行由模型判「哪个参数原厂未披露」再写进清单 —— 参数值有没有是台账里的事实,不该由模型宣布; + * 现在每颗料一张小表(外层按核对表逐颗复制,里层 `data-by="internal_pn"` 只列它名下的参数行), + * 人看得见现值;未披露的由差异评估判成 UNKNOWN 并挂验证任务。 */ +const PARAM_REF = `
+
${badge('st', 'exists_status')}
+
+ + + + + + + + + +
#参数名值单位约束类型说明
${ro('param_name')}${ro('param_value', 'mono')}${ro('param_unit')}${ro('param_note')}
+
`; + const GAP_TABLE = `
@@ -530,13 +564,16 @@ export const EVAL_INPUT_LIST = doc({ zone('做什么', '启动 4 项已绑定,其余信息在工单里补齐') + note([ 'AI 已完成:系统已按启动传入的 4 项完成绑定——客户、客户需求(新建 P-01 · 版本 V1)、原 PN、候选 PN(数组,逐个评估)——并生成输入快照。', - '需要你补齐:启动时未收的信息(应用场景、BOM 位)直接在下表补;默认值(替代类型 / 规则版本 / 负责人期限)可确认可改。本单缺口只覆盖「有而不全」;「完全没有」(料号不在库)已由数据工单创建。未补齐的缺口项将按 UNKNOWN 进入差异评估。', + '需要你补齐:启动时未收的信息(应用场景、BOM 位)直接在下表补;默认值(替代类型 / 规则版本 / 负责人期限)可确认可改。「完全没有」(料号不在库)已由数据工单创建;每颗料的参数现值摆在「候选参数现值」里,写着「未披露」的参数将按 UNKNOWN 进入差异评估并挂验证任务,不用在这里补。', ]) + zone('料号存在性核对', '只读参考 · 与下表「补充内容」互不冲突') + PN_CHECK_TABLE + + zone('候选参数现值', '只读参考 · P-08 标准化参数集,一颗料一张,基座从台账现读;写着「未披露」的由差异评估判成 UNKNOWN 并挂验证任务') + + PARAM_REF + zone('交回什么', '评估输入清单(已绑定项照实核对,缺口项补齐,*为必填)') + - gapbar(`${D}_input_gap`, '补充内容') + - editGate(`${D}.input_gap`, `还有 项待补 / 待确认,点「编辑」在「补充内容」列填入 —— 已绑定那几行的「输入项/来源/绑定值」抄的是启动入参,发现记录本身有误请退回触发方重新启动,不要在这里改`) + + // 红带说「必填格」不说「补充内容」:缺的可能是 AI 该写的输入项(2026-08-27 实测),写死列名会把人指向错的格子 + gapbar(`${D}_input_gap`, '必填格') + + editGate(`${D}.input_gap`, `还有 项待补,点「编辑」在「补充内容」列填入 —— 已绑定那几行的「输入项/来源/绑定值」抄的是启动入参,发现记录本身有误请退回触发方重新启动,不要在这里改`) + GAP_TABLE + addrow(`${D}.input_gap`, '+ 加一行(补充资料项)', '新行的「键」必须写(它是这一行的钥匙,写一个英文短名,如 thermal_note);「输入项」与「补充内容」也是必填;删一行 = 该输入项不适用;补不了的写「留 UNKNOWN」或「挂验证任务」') + flow( @@ -546,42 +583,59 @@ export const EVAL_INPUT_LIST = doc({ /* ==================== 局面 2 · 差异评估结论审核(搬自「验收工单-01」)==================== */ -const RELATION_SUMMARY_TABLE = `
#
+/** 候选 PN 结论汇总 —— 目标件 2026-08-27 改版:一个候选一条结论(P-11 主表草稿)。 + * 品牌不在 relation 上:用嵌套行模版 `data-by` 从 `parts.profile` 整表里挑这一颗料那一行(取值,不是算)。 + * 「差异统计(四态)」绑 difference_summary —— S3 剧本让模型按「7 项比对:4 MATCH / 1 MISMATCH / …」的形写它, + * 模版不数四态(数出来的画面一律不做假)。 */ +const RELATION_SUMMARY_TABLE = `
- - - - - - + + + + + + - - - + + + + -
#候选料号替代类型推荐等级风险等级推荐理由差异摘要候选 PN品牌推荐程度风险级别差异统计(四态)结论摘要
${badge('st rec', 'recommendation_level')}${badge('rec', 'recommendation_level')} ${badge('risk', 'risk_level')}
`; -const DIFFERENCE_ITEM_TABLE = `
- - - - - - - - - - - - - -
#参数项原 PN 值候选 PN 值单位约束类型结论风险等级证据引用AI 置信度
${badge('st dst', 'difference_status')}${badge('risk', 'risk_level')}
`; +/** 差异明细 · 按候选 PN 分组 —— 目标件 2026-08-27 改版:**一个候选一张表**(用户:「存在多个替代 PN 要对比差异时看不清楚」)。 + * 外层容器
按 relation 逐个复制行模版
,里层 + * 只列该候选名下的明细(渲染器 2026-08-27 补的嵌套 + 父键过滤)。容器标签名不得在行模版内部再出现: + *
里是 section / div / table,不撞。 */ +const CANDIDATE_SECTIONS = `
+
+ + + + vs 原 PN · + ${badge('rec', 'recommendation_level')}${badge('risk', 'risk_level')} +
+
+ + + + + + + + + + + + + +
#参数项原 PN 值候选 PN 值单位约束类型结论风险等级证据引用AI 置信度
${badge('st dst', 'difference_status')}${badge('risk', 'risk_level')}
+
`; export const DIFFERENCE_REVIEW = doc({ css: ACC1_CSS, @@ -595,18 +649,18 @@ export const DIFFERENCE_REVIEW = doc({ body: zone('AI 结论说明', '结论是怎么算出来的') + note([ - 'AI 已完成的计算:按 GOV 评估规则(锁定版本 ),对原 PN 与每个候选逐项比对,产出四态结论——MATCH / MISMATCH / UNKNOWN / CONDITIONAL,并给出风险等级、证据引用与置信度。差异明细 = P-11 差异明细子表(一条参数差异一条记录);当前为 AI 草稿,FAE 审核通过前不落账。评估参数可能含本次数据工单创建的主数据(确认人背书;未附 datasheet 的参数项会显式标注「人工录入」)。', + 'AI 已完成的计算:按 GOV 评估规则(锁定版本 ),以原 PN 为基准,对 个候选 PN 分别逐项比对,每项产出四态结论——MATCH / MISMATCH / UNKNOWN / CONDITIONAL,并给出风险等级、证据引用与置信度;再按候选汇总出下方的【候选 PN 结论汇总】(一个候选一条结论)。差异明细 = P-11 差异明细子表(一条参数差异一条记录,按候选分组);结论汇总 = P-11 主表草稿。当前均为 AI 草稿,FAE 审核通过前不落账。评估参数可能含本次数据工单创建的主数据(确认人背书;未附 datasheet 的参数项会显式标注「人工录入」)。', ]) + criteria('判断这份差异评估结论是否可信,请逐项核对', [ - '关键参数差异均有明确状态:逐项结论无漏项,每个参数都有四态之一的判定;', + '汇总结论与明细一致:结论表中每个候选的推荐程度 / 风险级别,与其下方差异明细表的四态分布吻合(存在 MISMATCH 硬约束或悬空 UNKNOWN 的候选不得给「推荐」);', + '关键参数差异均有明确状态:每个候选的逐项结论无漏项,每个参数都有四态之一的判定;', 'UNKNOWN 项均有去向:每个 UNKNOWN 都挂了验证任务或补来源动作,不允许悬空;', - '证据链完整:每项结论可追溯至 P-08 / P-10 / P-06 的具体记录,或本单数据工单的创建记录(标注:人工录入);', - '硬约束项未被自动判 MATCH:封装 / 工艺等硬约束未经证据支持不得判 MATCH。', + '证据链完整:每项结论可追溯至 P-08 / P-10 / P-06 的具体记录,或本单数据工单的创建记录(标注:人工录入);硬约束项未经证据支持不得判 MATCH。', ]) + - zone('各候选推荐等级', '只读展示(P-11 主记录草稿,一个候选一条)') + + zone('候选 PN 结论汇总', '每个候选一条结论,内容 = 对应下方明细表的汇总判定(P-11 主表草稿)') + RELATION_SUMMARY_TABLE + - zone('AI 产出的差异明细', '只读展示(P-11 差异明细子表草稿 · 一条参数一条记录 difference_item_id)') + - DIFFERENCE_ITEM_TABLE + + zone('差异明细 · 按候选 PN 分组', '每个候选一张表,与原 PN 逐项对比(P-11 差异明细子表草稿 · 一条参数一条记录 difference_item_id)') + + CANDIDATE_SECTIONS + flow( '审批:本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。审核意见与补充条件写入 P-06(验证要求 verification_requirement);通过后去向:进入步骤 5 · 评审放行与落账确认。', ), diff --git a/activities/part-substitution-assessment/src/views/target-style.ts b/activities/part-substitution-assessment/src/views/target-style.ts index 3026657..74bb165 100644 --- a/activities/part-substitution-assessment/src/views/target-style.ts +++ b/activities/part-substitution-assessment/src/views/target-style.ts @@ -208,13 +208,22 @@ tr.ro td{background:var(--soft)} table.on td>.jc-vcell.ro{display:block} `; +/* + * 两张验收件(ACC1 差异评估结论审核 / ACC2 评审放行与落账确认)的主题色**不再逐字节等于目标件**: + * 目标件的题头用 #b23a2e 红(--ledger),2026-08-27 用户定「中央 HTML 模版区域如果是验收工单, + * 标题部分的红色改为和验收卡一样的绿色」—— 与对话区验收卡同一枚 #0aa294(app.css --st-collab)。 + * 只换主题变量与题头渐变 / 徽章投影;ACC2 的 CONDITIONAL 状态徽标是语义色,保留原红。 + * ACC1 于 2026-08-27 下午随目标件改版整段重搬(「一个候选一张表」:.cand* / .rec* / .stat*,CONDITIONAL 改蓝), + * 主题色仍取卡的那一枚绿。 + */ export const ACC1_CSS = ` :root{ --ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e; --line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb; --brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff; - --ledger:#b23a2e; --ledger-soft:#f7e4e0; --ledger-line:#ecc5bd; + --ledger:#0aa294; --ledger-soft:#e4f8f5; --ledger-line:#b5e6df; /* 验收绿(2026-08-27 用户定:与对话区验收卡同色;目标件 2026-08-27 改版写的是 #16a34a,这里取卡的那一枚) */ --ok:#278717; --ok-soft:#e8f8e4; --danger:#d83931; --danger-soft:#feeae9; + --amber:#8f6a12; --amber-soft:#fdf3d7; --amber-line:#ecd9a0; --mono:"SF Mono",Menlo,Consolas,monospace; } *{box-sizing:border-box} @@ -222,8 +231,8 @@ html,body{margin:0;padding:0} body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:#eef1f6;color:var(--ink);font-size:14px;line-height:1.7;-webkit-font-smoothing:antialiased} .page{margin:0;padding:0} .card{background:#fff;overflow:hidden;border:0;border-radius:0;box-shadow:none} -.card-head{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:2px solid var(--ink);background:linear-gradient(120deg,#fdf3f1 0%,#fff 55%,#fbf2f0 100%)} -.badge{flex:none;width:44px;height:44px;border-radius:10px;display:grid;place-items:center;background:var(--ledger);color:#fff;font-weight:800;font-size:13px;box-shadow:0 6px 16px rgba(178,58,46,.25)} +.card-head{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:2px solid var(--ink);background:linear-gradient(120deg,#eefaf8 0%,#fff 55%,#ecf8f5 100%)} +.badge{flex:none;width:44px;height:44px;border-radius:10px;display:grid;place-items:center;background:var(--ledger);color:#fff;font-weight:800;font-size:13px;box-shadow:0 6px 16px rgba(10,162,148,.25)} .tit{flex:1;min-width:0} .tit .kick{font-size:11px;letter-spacing:.08em;color:var(--ink-4);font-weight:700} .tit h1{margin:2px 0 0;font-size:19px;font-weight:800;line-height:1.3} @@ -249,27 +258,43 @@ table{width:100%;border-collapse:collapse;font-size:13px} th{text-align:left;background:var(--soft);font-size:11px;letter-spacing:.05em;font-weight:800;color:var(--ink-3);padding:9px 12px;border-bottom:1px solid var(--line)} td{padding:11px 12px;border-bottom:1px solid var(--line-soft);vertical-align:top} tr:last-child td{border-bottom:0} +.cand .tbl-wrap{border:0;border-radius:0} .num{font-family:var(--mono);color:var(--ink-4);white-space:nowrap} .cust{font-weight:700} -.prio{font-family:var(--mono);font-weight:800} -.prio.hi{color:var(--danger)} -.prio.mid{color:var(--ink-2)} .reason{font-size:12.5px;color:var(--ink-2)} .flow{margin-top:16px;padding:9px 12px;border-radius:8px;background:var(--soft);border:1px dashed var(--line);font-size:12px;color:var(--ink-3)} .flow b{color:var(--ink)} .card-foot{padding:13px 22px;border-top:1px solid var(--line-soft);font-size:11.5px;color:var(--ink-4);display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px} @media(max-width:600px){.card-head{flex-wrap:wrap}.side{align-items:flex-start}} -/* 补充:四态结论 pill 与风险等级(仅追加,不改动基件样式) */ +/* 四态结论 pill 与风险等级 */ .st{display:inline-block;padding:1px 9px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.04em;font-family:var(--mono);white-space:nowrap;border:1px solid transparent} .st.ok{color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4} -.st.miss{color:var(--danger);background:var(--danger-soft);border-color:#f3b9b5} -.st.unknown{color:#8f6a12;background:#fdf3d7;border-color:#ecd9a0} -.st.cond{color:var(--ledger);background:var(--ledger-soft);border-color:var(--ledger-line)} +.st.miss{color:var(--danger);background:#feeae9;border-color:#f3b9b5} +.st.unknown{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)} +.st.cond{color:var(--brand-deep);background:var(--brand-wash);border-color:var(--brand-soft)} .risk{font-weight:800;font-size:12.5px} .risk.low{color:var(--ok)} -.risk.mid{color:#8f6a12} +.risk.mid{color:var(--amber)} .risk.hi{color:var(--danger)} .mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)} +/* 推荐程度 pill */ +.rec{display:inline-block;padding:2px 11px;border-radius:999px;font-size:11.5px;font-weight:800;white-space:nowrap;border:1px solid transparent} +.rec.yes{color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4} +.rec.cond{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)} +.rec.no{color:var(--danger);background:#feeae9;border-color:#f3b9b5} +/* 差异统计(结论表内) */ +.stat{font-family:var(--mono);font-size:11.5px;color:var(--ink-3);white-space:nowrap} +.stat b{font-weight:800} +.stat .m{color:var(--ok)} .stat .x{color:var(--danger)} .stat .u{color:var(--amber)} .stat .c{color:var(--brand-deep)} +/* 候选 PN 明细块:一个候选一张表 */ +.cand{border:1px solid var(--line-soft);border-radius:10px;overflow:hidden;margin-bottom:16px} +.cand:last-of-type{margin-bottom:0} +.cand-head{display:flex;align-items:center;gap:10px;padding:11px 14px;background:linear-gradient(120deg,#f6f8fc 0%,#fff 70%);border-bottom:1px solid var(--line);flex-wrap:wrap} +.cand-idx{flex:none;width:22px;height:22px;border-radius:6px;display:grid;place-items:center;background:var(--ink);color:#fff;font-size:11px;font-weight:800;font-family:var(--mono)} +.cand-name{font-weight:800;font-size:14px} +.cand-brand{color:var(--ink-3);font-size:12px} +.cand-vs{font-family:var(--mono);font-size:11.5px;color:var(--ink-4)} +.cand-con{margin-left:auto;display:flex;align-items:center;gap:8px} `; export const ACC2_CSS = ` @@ -277,7 +302,7 @@ export const ACC2_CSS = ` --ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e; --line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb; --brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff; - --ledger:#b23a2e; --ledger-soft:#f7e4e0; --ledger-line:#ecc5bd; + --ledger:#0aa294; --ledger-soft:#e4f8f5; --ledger-line:#b5e6df; /* 验收绿(2026-08-27 用户定:与对话区验收卡同色,原目标件是 #b23a2e 红) */ --ok:#278717; --ok-soft:#e8f8e4; --danger:#d83931; --danger-soft:#feeae9; --mono:"SF Mono",Menlo,Consolas,monospace; } @@ -286,8 +311,8 @@ html,body{margin:0;padding:0} body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:#eef1f6;color:var(--ink);font-size:14px;line-height:1.7;-webkit-font-smoothing:antialiased} .page{margin:0;padding:0} .card{background:#fff;overflow:hidden;border:0;border-radius:0;box-shadow:none} -.card-head{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:2px solid var(--ink);background:linear-gradient(120deg,#fdf3f1 0%,#fff 55%,#fbf2f0 100%)} -.badge{flex:none;width:44px;height:44px;border-radius:10px;display:grid;place-items:center;background:var(--ledger);color:#fff;font-weight:800;font-size:13px;box-shadow:0 6px 16px rgba(178,58,46,.25)} +.card-head{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:2px solid var(--ink);background:linear-gradient(120deg,#eefaf8 0%,#fff 55%,#ecf8f5 100%)} +.badge{flex:none;width:44px;height:44px;border-radius:10px;display:grid;place-items:center;background:var(--ledger);color:#fff;font-weight:800;font-size:13px;box-shadow:0 6px 16px rgba(10,162,148,.25)} .tit{flex:1;min-width:0} .tit .kick{font-size:11px;letter-spacing:.08em;color:var(--ink-4);font-weight:700} .tit h1{margin:2px 0 0;font-size:19px;font-weight:800;line-height:1.3} @@ -328,7 +353,7 @@ tr:last-child td{border-bottom:0} .st.ok{color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4} .st.miss{color:var(--danger);background:var(--danger-soft);border-color:#f3b9b5} .st.unknown{color:#8f6a12;background:#fdf3d7;border-color:#ecd9a0} -.st.cond{color:var(--ledger);background:var(--ledger-soft);border-color:var(--ledger-line)} +.st.cond{color:#b23a2e;background:#f7e4e0;border-color:#ecc5bd} .risk{font-weight:800;font-size:12.5px} .risk.mid{color:#8f6a12} .mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)} diff --git a/docs/ACT-WORK-05-端到端测试记录.md b/docs/ACT-WORK-05-端到端测试记录.md index 9981c0f..f1d0780 100644 --- a/docs/ACT-WORK-05-端到端测试记录.md +++ b/docs/ACT-WORK-05-端到端测试记录.md @@ -235,6 +235,8 @@ P-11 一条(`LM2596S-3.3 → LM2596S-ADJ | alternate | fae_confirmed`);P-01 回 | X | 两颗缺库只问了一次;`expectDecision` 只看张数,审核卡就把它喂饱 | 剧本「还有 absent → 写骨架再发卡」把「先问」压掉;断言太宽 | 剧本补「第二颗也要先问」;脚本新增 `expectAsks`(按问法数) | 活动 + 脚本 | | Y | 模型漏铺骨架 → 闸如实拦「一行都没有」→ 脚本只会填已有行,连吃 400 抛出 | 脚本没有「加一行」这条路,而人在浏览器里正是这么做的 | 主键齐了就加一行(与件上「加一行」同一条路) | 脚本 | | Z | 铺完骨架就标 `created`、直接 finish,撞「从没给它发过卡」连拒五拍 | 我 1.3.1 的措辞「表里真有一行才标 created」—— 而骨架是模型自己铺的 | 「created 认的是卡交回来了」;顺序写死:骨架 → 发卡 → 拍结束 → 人交回 → 才标 | 活动 | +| AB | **库里有值的参数被 S1 清单当成缺口再问一遍**(三次复现):人在 P-08 填了使能阈值 2.5,清单上仍有「使能阈值 · 原厂未披露」;第三次连数据工单都没派(料号已在库、21 个参数全有值)照样出现 | S1 的「待确认」行是模型对参数表的断言,基座无读数核它;说明书里「使能阈值」与「未披露」同句三处(含 1.4.10 我写的反例原话);S-FEEL 无列表过滤写不出判据 | S1 一行参数都不写;件上加「候选参数现值」只读块(嵌套 `data-by` 现读台账);未披露归 S3 判 UNKNOWN;说明书全文去掉「使能阈值」 | 活动(结构)+ 平台(渲染器嵌套行) | +| AA | **数据工单确认了,数据没入库**:卡上写着「确认后这批主数据即入库」,人建好的 P-08/P-10 记录要等整条评估放行才进正式表;试跑单(不落主数据)永远不进(2026-08-27 用户指出) | 草稿层通往正式表只有整单放行一道口子(`releaseWrite(taskId)`),回传路由只记办结 + 抛人事件;而引擎的 `mine:` / `_rows` / 哨兵全靠草稿层认「本单写下的行」,清掉就断 | `releaseWrite(taskId, {only, cause})` 范围放行:只并这张卡 `gateLedgers` 那几张表,并过的行**留在草稿层打 `landed` 记号**(读面不变,整单放行不再写);回传路由在保存与回传落定后调它,痕点名几张表各几行;`createOnly` / `drop` 对已落地行改口(别 drop,撤了读面少一行);活动 1.4.8 剧本改口「已入正式表」 | **平台**(结构)+ 活动 | **可推广的三条**(已记 `docs/debt.md`): diff --git a/docs/SS02-ACT-WORK-05-html/ACT-WORK-05-验收工单-01-差异评估结论审核.html b/docs/SS02-ACT-WORK-05-html/ACT-WORK-05-验收工单-01-差异评估结论审核.html index 3093802..66f2119 100644 --- a/docs/SS02-ACT-WORK-05-html/ACT-WORK-05-验收工单-01-差异评估结论审核.html +++ b/docs/SS02-ACT-WORK-05-html/ACT-WORK-05-验收工单-01-差异评估结论审核.html @@ -9,8 +9,9 @@ --ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e; --line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb; --brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff; - --ledger:#b23a2e; --ledger-soft:#f7e4e0; --ledger-line:#ecc5bd; + --ledger:#16a34a; --ledger-soft:#e6f6ec; --ledger-line:#b7e5c6; --ok:#278717; --ok-soft:#e8f8e4; --danger:#d83931; --danger-soft:#feeae9; + --amber:#8f6a12; --amber-soft:#fdf3d7; --amber-line:#ecd9a0; --mono:"SF Mono",Menlo,Consolas,monospace; } *{box-sizing:border-box} @@ -18,8 +19,8 @@ html,body{margin:0;padding:0} body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:#eef1f6;color:var(--ink);font-size:14px;line-height:1.7;-webkit-font-smoothing:antialiased} .page{margin:0;padding:0} .card{background:#fff;overflow:hidden;border:0;border-radius:0;box-shadow:none} -.card-head{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:2px solid var(--ink);background:linear-gradient(120deg,#fdf3f1 0%,#fff 55%,#fbf2f0 100%)} -.badge{flex:none;width:44px;height:44px;border-radius:10px;display:grid;place-items:center;background:var(--ledger);color:#fff;font-weight:800;font-size:13px;box-shadow:0 6px 16px rgba(178,58,46,.25)} +.card-head{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:2px solid var(--ink);background:linear-gradient(120deg,#f0faf3 0%,#fff 55%,#ecf7f0 100%)} +.badge{flex:none;width:44px;height:44px;border-radius:10px;display:grid;place-items:center;background:var(--ledger);color:#fff;font-weight:800;font-size:13px;box-shadow:0 6px 16px rgba(22,163,74,.28)} .tit{flex:1;min-width:0} .tit .kick{font-size:11px;letter-spacing:.08em;color:var(--ink-4);font-weight:700} .tit h1{margin:2px 0 0;font-size:19px;font-weight:800;line-height:1.3} @@ -45,27 +46,43 @@ table{width:100%;border-collapse:collapse;font-size:13px} th{text-align:left;background:var(--soft);font-size:11px;letter-spacing:.05em;font-weight:800;color:var(--ink-3);padding:9px 12px;border-bottom:1px solid var(--line)} td{padding:11px 12px;border-bottom:1px solid var(--line-soft);vertical-align:top} tr:last-child td{border-bottom:0} +.cand .tbl-wrap{border:0;border-radius:0} .num{font-family:var(--mono);color:var(--ink-4);white-space:nowrap} .cust{font-weight:700} -.prio{font-family:var(--mono);font-weight:800} -.prio.hi{color:var(--danger)} -.prio.mid{color:var(--ink-2)} .reason{font-size:12.5px;color:var(--ink-2)} .flow{margin-top:16px;padding:9px 12px;border-radius:8px;background:var(--soft);border:1px dashed var(--line);font-size:12px;color:var(--ink-3)} .flow b{color:var(--ink)} .card-foot{padding:13px 22px;border-top:1px solid var(--line-soft);font-size:11.5px;color:var(--ink-4);display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px} @media(max-width:600px){.card-head{flex-wrap:wrap}.side{align-items:flex-start}} -/* 补充:四态结论 pill 与风险等级(仅追加,不改动基件样式) */ +/* 四态结论 pill 与风险等级 */ .st{display:inline-block;padding:1px 9px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.04em;font-family:var(--mono);white-space:nowrap;border:1px solid transparent} .st.ok{color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4} -.st.miss{color:var(--danger);background:var(--danger-soft);border-color:#f3b9b5} -.st.unknown{color:#8f6a12;background:#fdf3d7;border-color:#ecd9a0} -.st.cond{color:var(--ledger);background:var(--ledger-soft);border-color:var(--ledger-line)} +.st.miss{color:var(--danger);background:#feeae9;border-color:#f3b9b5} +.st.unknown{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)} +.st.cond{color:var(--brand-deep);background:var(--brand-wash);border-color:var(--brand-soft)} .risk{font-weight:800;font-size:12.5px} .risk.low{color:var(--ok)} -.risk.mid{color:#8f6a12} +.risk.mid{color:var(--amber)} .risk.hi{color:var(--danger)} .mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)} +/* 推荐程度 pill */ +.rec{display:inline-block;padding:2px 11px;border-radius:999px;font-size:11.5px;font-weight:800;white-space:nowrap;border:1px solid transparent} +.rec.yes{color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4} +.rec.cond{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)} +.rec.no{color:var(--danger);background:#feeae9;border-color:#f3b9b5} +/* 差异统计(结论表内) */ +.stat{font-family:var(--mono);font-size:11.5px;color:var(--ink-3);white-space:nowrap} +.stat b{font-weight:800} +.stat .m{color:var(--ok)} .stat .x{color:var(--danger)} .stat .u{color:var(--amber)} .stat .c{color:var(--brand-deep)} +/* 候选 PN 明细块:一个候选一张表 */ +.cand{border:1px solid var(--line-soft);border-radius:10px;overflow:hidden;margin-bottom:16px} +.cand:last-of-type{margin-bottom:0} +.cand-head{display:flex;align-items:center;gap:10px;padding:11px 14px;background:linear-gradient(120deg,#f6f8fc 0%,#fff 70%);border-bottom:1px solid var(--line);flex-wrap:wrap} +.cand-idx{flex:none;width:22px;height:22px;border-radius:6px;display:grid;place-items:center;background:var(--ink);color:#fff;font-size:11px;font-weight:800;font-family:var(--mono)} +.cand-name{font-weight:800;font-size:14px} +.cand-brand{color:var(--ink-3);font-size:12px} +.cand-vs{font-family:var(--mono);font-size:11.5px;color:var(--ink-4)} +.cand-con{margin-left:auto;display:flex;align-items:center;gap:8px} @@ -90,113 +107,275 @@ tr:last-child td{border-bottom:0}

【AI 结论说明】—— 结论是怎么算出来的

-

AI 已完成的计算:按 GOV 评估规则(锁定版本 R-2026.07,rule_version),对 Original=LM2596S-5.0(TI) / Candidate=XL2596S-5.0(XLSEMI) 逐项比对,产出四态结论——MATCH / MISMATCH / UNKNOWN / CONDITIONAL,并给出风险等级、证据引用与置信度。差异明细 = P-11 差异明细子表(一条参数差异一条记录,difference_item_id);当前为 AI 草稿,FAE 审核通过前不落账。评估参数可能含本次数据工单创建的主数据(确认人背书;未附 datasheet 的参数项会显式标注「人工录入」)。

+

AI 已完成的计算:按 GOV 评估规则(锁定版本 R-2026.07,rule_version),以 Original=LM2596S-5.0(TI) 为基准,对 2 个候选 PN 分别逐项比对,每项产出四态结论——MATCH / MISMATCH / UNKNOWN / CONDITIONAL,并给出风险等级、证据引用与置信度;再按候选汇总出下方的【候选 PN 结论汇总】(一个候选一条结论)。差异明细 = P-11 差异明细子表(一条参数差异一条记录,difference_item_id,按 candidate_pn 分组);结论汇总 = P-11 主表草稿。当前均为 AI 草稿,FAE 审核通过前不落账。评估参数可能含本次数据工单创建的主数据(确认人背书;未附 datasheet 的参数项会显式标注「人工录入」)。

【验收判据】—— 你据此判断「通过 / 驳回」

判断这份差异评估结论是否可信,请逐项核对
    -
  • 关键参数差异均有明确状态:逐项结论无漏项,每个参数都有四态之一的判定;
  • +
  • 汇总结论与明细一致:结论表中每个候选的推荐程度 / 风险级别,与其下方差异明细表的四态分布吻合(存在 MISMATCH 硬约束或悬空 UNKNOWN 的候选不得给「推荐」);
  • +
  • 关键参数差异均有明确状态:每个候选的逐项结论无漏项,每个参数都有四态之一的判定;
  • UNKNOWN 项均有去向:每个 UNKNOWN 都挂了验证任务(如 VAL-2026-0188)或补来源动作,不允许悬空;
  • -
  • 证据链完整:每项结论可追溯至 P-08 / P-10 / P-06 或 EXT-02 的具体记录,或追溯至数据工单创建记录(标注:人工录入 · 数据工单号 · 确认人);
  • -
  • 硬约束项未被自动判 MATCH:封装 / 工艺等硬约束未经证据支持不得判 MATCH。
  • +
  • 证据链完整:每项结论可追溯至 P-08 / P-10 / P-06 或 EXT-02 的具体记录,或追溯至数据工单创建记录(标注:人工录入 · 数据工单号 · 确认人);硬约束项未经证据支持不得判 MATCH。
-

【AI 产出的差异明细】—— 只读展示(P-11 差异明细子表草稿 · 一条参数一条记录 difference_item_id)

-
+

【候选 PN 结论汇总】—— 每个候选一条结论,内容 = 对应下方明细表的汇总判定(P-11 主表草稿)

+
- - + + + + + + + + + + + + - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + +
#参数项 parameter_name原 PN 值 original_value候选 PN 值 candidate_value单位 parameter_unit约束类型 constraint_type结论 difference_status风险等级证据引用 evidence_refAI 置信度
#候选 PN品牌推荐程度风险级别差异统计(四态)结论摘要
1输出电压5.05.0V硬约束MATCH低P-08 · P-100.98XL2596S-5.0XLSEMI 芯龙有条件推荐中一致 4 · 差异 1 · 未知 1 · 附条件 1电气主参数一致、工作温度覆盖更宽;使能阈值未披露需挂验证,生命周期凭供货承诺函附条件通过。
2输出电流33A硬约束MATCH低P-08 · P-100.97
3开关频率150150kHz硬约束MATCH低P-08 · P-100.95
4封装TO-263-5TO-263-5—硬约束MATCH低P-08 · P-100.99
5工作温度-40~+85-40~+125℃硬约束MISMATCHAP1501A-5.0Diodes不推荐 高P-100.93
6使能阈值1.4未披露V硬约束UNKNOWN中挂 VAL-2026-0188—
7生命周期ActiveActive—软约束CONDITIONAL中P-10(候选供货承诺函)0.90一致 3 · 差异 2 · 未知 1使能逻辑与原设计相反,替换需改原理图;静态电流偏高。替换代价大于收益。
+

【差异明细 · 按候选 PN 分组】—— 每个候选一张表,与原 PN 逐项对比(P-11 差异明细子表草稿 · 一条参数一条记录 difference_item_id)

+ +
+
+ 1 + XL2596S-5.0 + XLSEMI 芯龙 + vs 原 PN · LM2596S-5.0(TI) + 有条件推荐风险 中 +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#参数项原 PN 值候选 PN 值单位约束类型结论风险等级证据引用AI 置信度
1输出电压5.05.0V硬约束MATCH低P-08 · P-100.98
2输出电流33A硬约束MATCH低P-08 · P-100.97
3开关频率150150kHz硬约束MATCH低P-08 · P-100.95
4封装TO-263-5TO-263-5—硬约束MATCH低P-08 · P-100.99
5工作温度-40~+85-40~+125℃硬约束MISMATCH高P-100.93
6使能阈值1.4未披露V硬约束UNKNOWN中挂 VAL-2026-0188—
7生命周期ActiveActive—软约束CONDITIONAL中P-10(候选供货承诺函)0.90
+
+
+ +
+
+ 2 + AP1501A-5.0 + Diodes + vs 原 PN · LM2596S-5.0(TI) + 不推荐风险 高 +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#参数项原 PN 值候选 PN 值单位约束类型结论风险等级证据引用AI 置信度
1输出电压5.05.0V硬约束MATCH低P-08 · P-100.98
2输出电流33A硬约束MATCH低P-08 · P-100.96
3开关频率150150kHz硬约束MATCH低P-08 · P-100.94
4使能逻辑高电平有效低电平有效—硬约束MISMATCH高P-100.92
5静态电流1020mA软约束MISMATCH中P-100.90
6使能阈值1.4未披露V硬约束UNKNOWN中挂 VAL-2026-0189—
+
+
+
审批:本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。审核意见与补充条件写入 P-06(验证要求 verification_requirement);通过后去向:进入步骤 5 · 评审放行与落账确认。
diff --git a/docs/ss02-act-05/ACT-WORK-05-关键台账数据-数据溯源.md b/docs/ss02-act-05/ACT-WORK-05-关键台账数据-数据溯源.md new file mode 100644 index 0000000..b123163 --- /dev/null +++ b/docs/ss02-act-05/ACT-WORK-05-关键台账数据-数据溯源.md @@ -0,0 +1,143 @@ +# ACT-WORK-05 关键台账数据看板 · 数据溯源说明 + +对应页面:`ACT-WORK-05-关键台账数据.html` + +本文档逐项说明看板中**每一个数据的取值来源**。来源分两类: + +- **直读**:取自台账数据文件(Excel),逐字段一一对应; +- **推导**:该台账暂无独立数据文件,由 P-08 / P-06 记录中的引用关系、参数集比对或活动流程规则推导得出,用于演示(页面图例中的琥珀色点)。 + +## 数据来源文件 + +| 来源 | 路径 | 说明 | +|---|---|---| +| P-08 数据文件 | `activity/SS02-需求分析与方案匹配/P-08-芯片主数据台账-模拟数据V0.1.xlsx`(sheet「字段总表」,共 20 条) | 直读 | +| P-06 数据文件 | `activity/SS02-需求分析与方案匹配/P-06-样品验证记录台账-模拟数据V0.1.xlsx`(sheet「字段总表」,共 20 条) | 直读 | +| 字段口径 | `SS02-SS03-SS04_Demo台账字段细化与统一方案.md` | 台账字段定义依据 | +| 活动逻辑 | `ACT-WORK-05-活动流程与工单说明.md`、`activity/SS02-需求分析与方案匹配/SS02_ACT-WORK-05_差异评估_E04-I01架构版_V1.2.html` | 推导规则依据 | + +--- + +## 一、页头(mast) + +| 看板上的值 | 来源 | 取值说明 | +|---|---|---| +| REQ-20260826-001 | 直读 | P-06 文件,记录 P06-0001(VAL-2026-0188)的 `request_id` 字段 | +| CUS-2025-0007 | 直读 | P-06 文件 P06-0001 的 `customer_id` 字段 | +| PRJ-PSU-40V | 直读 | P-06 文件 P06-0001 的 `project_id` 字段 | +| LM2596S-5.0(TI)/ XL2596S-5.0(XLSEMI)/ LM2596S-ADJ(TI) | 直读 | P-08 文件记录 P08-0001 / P08-0002 / P08-0003 的 `internal_pn` + `brand_name` 字段 | +| 规则版本 R-2026.07 | 推导 | GOV 评估规则库在本次运行锁定的版本号,演示设定值(GOV 为系统级台账,无数据文件) | + +## 二、重要性分层条(5 张分层卡) + +5 张卡片的台账编号、名称、角色描述(核心输入/核心产出/证据链/闭环去向/上下文)均来自《Demo台账字段细化与统一方案.md》中各台账的定义,以及《ACT-WORK-05-活动流程与工单说明.md》中本活动对各台账的读写关系(P-08 查无硬阻塞、P-11 落账、P-10 证据、P-06 收口、P-01 回写)。**推导**(角色定位描述,非台账数据)。 + +## 三、P-01 客户需求主台账(KV 卡,标注「回链推导 · 1 条」) + +P-01 无数据文件,整条记录为推导展示: + +| 看板字段 | 值 | 来源 | +|---|---|---| +| 需求编号 | REQ-20260826-001 | 直读链:P-06 P06-0001.`request_id` | +| 客户 | CUS-2025-0007 | 直读链:P-06 P06-0001.`customer_id` | +| 项目 | PRJ-PSU-40V | 直读链:P-06 P06-0001.`project_id` | +| 需求版本 | V1(启动新建) | 推导:活动「一键启动」规则——口述需求无 request_id 时启动新建 V1 | +| 需求描述 | 工业电源 12V→5V 电源位替代:原 LM2596S-5.0(NRND,PCN-2025-118 通知 2027-03 停产) | 推导:由 P-08 P08-0001.`lifecycle_status`/`eol_pcn_status` + P-06 P06-0001.`test_condition`(U3 电源位、12V 输入)拼装 | +| 应用场景 | 12V/24V/40V 输入三档 · 0.5A~3A 负载 · 工业温域 | 推导:P-06 P06-0001.`test_condition`(输入三档、负载三档)+ P-08 P08-0001.`application_scenarios`(工业电源) | +| BOM 位 | U3 电源位 | 直读链:P-06 P06-0001.`test_condition`「12V→5V 电源位(U3)」 | +| 原内部料号 | LM2596S-5.0 | 直读链:P-08 P08-0001.`internal_pn` | +| 候选内部料号 | XL2596S-5.0 · LM2596S-ADJ | 直读链:P-08 P08-0002/P08-0003.`internal_pn` | +| 风险等级(回写) | 中 | 推导:P-11-SUB-0001 风险等级(见下),活动结束回写 P-01 | +| 验证要求(回写) | VAL-2026-0188 | 直读链:P-06 P06-0001.`validation_id`,由其 `next_validation_action`「回链 P-11-SUB-0001」挂接 | +| 当前阶段 | 提交 ACT-GATE-03 | 推导:活动流程——验收工单02 放行后进入 GATE-03 评审 | +| 下一步动作 | 提交 ACT-GATE-03 评审放行(FAE · 李工 · 1 个工作日) | 推导:活动流程 SLA;「李工」直读链自 P-08/P-06 的 `data_owner`=FAE·李工 | + +## 四、P-08 芯片主数据台账(表,标注「数据文件直读 · 3 条」) + +三行分别对应 P-08 文件的记录 **P08-0001 / P08-0002 / P08-0003**,全部为直读。列与字段映射: + +| 看板列 | P-08 字段 | 备注 | +|---|---|---| +| 内部料号(如 LM2596S-5.0) | `internal_pn` | 第二行小字「P08-0001 · v3」= `record_id` + `record_version` | +| 品牌 / 制造商 | `brand_name` / `manufacturer_name` | | +| 规格描述 | `specification_description` | 页面做了截短展示(如 P08-0001 原文末尾「开关频率,TO-263-5 封装」未显示,封装已单列) | +| 封装 / 工艺 | `package` / `process_technology` | 三条均为 TO-263-5 / BCD | +| 生命周期 | `lifecycle_status` + `eol_pcn_status` | P08-0001=NRND +「PCN-2025-118 已通知,2027-03 停止供货」;另两条 Active | +| Datasheet | `datasheet_version` | Rev H(2023-09)/ Rev C(2025-06)/ Rev H(2023-09) | +| 标准化参数集(8 项 chips) | `normalized_parameter_set`(JSON) | 8 个瓷贴 = JSON 里 8 个参数逐项直读:输入电压范围/输出电压/输出电流/开关频率/静态电流/工作温度/使能阈值/反馈网络。P08-0002 的「EN 阈值 未披露」即 JSON 原值「未披露」 | +| 适配场景 | `application_scenarios` | | + +参数集版本「v2026-08」(差异明细表证据列中引用)= JSON 内「参数集版本」字段。 + +## 五、P-11 替代选型与批准关系库(标注「回链推导 · 2 条」+ 差异明细 6 条) + +P-11 无数据文件。主记录 2 条: + +| 看板字段 | P11-SUB-0001 的值 | P11-SUB-0002 的值 | 来源 | +|---|---|---|---| +| 替代关系 ID | P11-SUB-0001 | P11-SUB-0002 | 直读链:P08-0001/0002.`pin_to_pin_relation_ref`=P11-SUB-0001;P08-0003.=P11-SUB-0002 | +| 原 → 候选 | LM2596S-5.0 → XL2596S-5.0 | LM2596S-5.0 → LM2596S-ADJ | 推导:由 P-08 记录的三方引用关系确定 | +| 替代类型 | Pin-to-Pin | 功能替代 | 直读链:P-06 P06-0001.`validation_type`=Pin-to-Pin;ADJ 需改反馈网络,按规则判为功能替代 | +| 推荐等级 | 条件推荐 | 备选 | 直读链:P06-0001.`next_validation_action`「未闭环前维持**条件推荐**」;P06-0017(VAL-2026-0040).`next_validation_action`「作为**备选**结论回链 P-11-SUB-0002」 | +| 分析状态 | FAE确认 | FAE确认 | 推导:流程状态——差异评估已完成、待 GATE-03,FAE=李工(data_owner) | +| 风险 | 中 | 低 | 直读链:P06-0001.`issue_risk`「…风险=中」;P06-0017.`issue_risk`「…风险=低」 | +| 验证要求 / 回链 | VAL-2026-0188(使能阈值实测,进行中) | VAL-2026-0040(条件通过:外部分压精度 ±2.1%) | 直读链:两条 P-06 记录的 `validation_id` / `validation_status` / `test_result` | +| 规则版本 | R-2026.07 | R-2026.07 | 推导:演示锁定的 GOV 规则版本 | +| 客户批准(只读) | 未提交 | 未提交 | 推导:流程未走到客户批准节点 | + +差异明细子表 6 条(非 MATCH 项),由双方 P-08 `normalized_parameter_set` 逐项比对**推导**得出: + +| 所属 | 参数项 | 原值 → 候选值 | 结论/风险 | 证据引用 | 取值来源 | +|---|---|---|---|---|---| +| SUB-0001 | 生命周期 | NRND(PCN-2025-118)→ Active | MISMATCH · 高 | P-08 · P08-0001/0002 | 两记录 `lifecycle_status`/`eol_pcn_status` 直读比对;「本次替代的驱动因素」为影响说明(推导) | +| SUB-0001 | 工作温度 | -40~+85℃ → -40~+125℃ | MISMATCH · 中 | P-08 参数集 v2026-08 | 参数集 JSON 直读比对;「候选温域更宽,覆盖原需求」为影响说明 | +| SUB-0001 | 使能阈值 | 1.4V → 未披露 | UNKNOWN · 中 | 挂 VAL-2026-0188 | 参数集 JSON 直读比对;UNKNOWN 收口动作直读链自 P06-0001(`validation_id`/`issue_risk`/`next_validation_action`) | +| SUB-0001 | 静态电流 | 5mA → 4.2mA | MISMATCH · 低 | P-08 参数集 v2026-08 | 参数集 JSON 直读比对;「候选更优」为影响说明 | +| SUB-0002 | 输出电压 | 5.0V 固定 → 1.23~37V 可调 | CONDITIONAL · 中 | VAL-2026-0040 | 参数集 JSON 直读比对;「精度 ±2.1%(固定版 ±1%)」直读链自 P06-0017.`test_result` | +| SUB-0002 | 反馈网络 | 内置固定分压 → 外部分压电阻 | CONDITIONAL · 中 | VAL-2026-0040 | 参数集 JSON 直读比对;「BOM 增加 2 颗 0.1% 电阻」由 P06-0017.`issue_risk`「精度敏感场景需 0.1% 电阻」推导 | + +约束列(硬约束/软约束):推导——电性能参数按 GOV 规则默认硬约束,生命周期/静态电流按软约束处理(演示口径)。 + +## 六、P-10 原厂技术资料库(标注「回链推导 · 2 条」) + +P-10 无数据文件,两条均由 P-08 的 `datasheet_ref` / `datasheet_version` / `brand_name` 直读链推导: + +| 行 | 关联料号 | 资料文件 | 版本 | 来源 | +|---|---|---|---|---| +| 1 | LM2596S-5.0 · LM2596S-ADJ | ti.com/lit/ds/snvsbt0/snvsbt0.pdf | Rev H(2023-09) | P08-0001 与 P08-0003 的 `datasheet_ref` 相同(两条 TI 料共用一份 datasheet),合并为一条;页面显示去掉了 `https://www.` 前缀 | +| 2 | XL2596S-5.0 | xlsemi.com/datasheet/XL2596S-5.0.pdf | Rev C(2025-06) | P08-0002.`datasheet_ref` / `datasheet_version` | + +「资料类型 = Datasheet」「状态 = 有效」为推导(`record_status`=有效 沿用)。 + +## 七、P-06 样品验证记录台账(表,标注「数据文件直读 · 3 条」) + +三行对应 P-06 文件记录 **P06-0001(VAL-2026-0188)/ P06-0017(VAL-2026-0040)/ P06-0018(VAL-2026-0032)**,全部直读。列与字段映射: + +| 看板列 | P-06 字段 | 备注 | +|---|---|---| +| 验证 ID | `validation_id` | | +| 关联需求 / 项目 | `request_id` / `project_id` | VAL-2026-0040 的 project_id 原文为「—」,页面如实显示 | +| 验证料号 | `validation_internal_pn` | | +| 验证类型 | `validation_type` | Pin-to-Pin / 功能 / 可靠性 | +| 测试条件 | `test_condition` | VAL-2026-0188 原文前缀「样品上板验证:」页面简写为「样品上板:」,其余原文 | +| 结果 / 状态 | `validation_status` + `test_result` | 状态做 pill;`test_result` 原文放入小字(如「排期 2026-08-28」「精度 ±2.1%(固定版 ±1%),温漂 +0.6%」「壳温 108℃(加散热片),输出漂移 +0.8%」);批次号 = `sample_lot`(XL-202607-03);RPT 编号 = `evidence_ref`(RPT-2026-0040 / RPT-2026-0032,页面去掉 .pdf 后缀) | +| 问题与风险 | `issue_risk` | 原文直读;VAL-2026-0032 原文「—」如实显示 | +| 后续动作 | `next_validation_action` | 原文直读;VAL-2026-0032 的「(作为本次评估历史证据引用)」为页面补充说明(推导) | + +未展示字段:`record_id`、`record_version`、`customer_id`(已在页头)、`sample_version`、`data_owner`、`created_at`/`updated_at`/`last_verified_at` 等管理字段。 + +## 八、次要台账条(P-09 / EXT-02 / GOV) + +三张卡片只有台账编号、名称和一句角色描述,**无台账数据**。描述文字为推导,依据《Demo台账字段细化与统一方案.md》的台账定义 + 本活动读写关系(P-09 提供 BOM 位、EXT-02 登记可信外部来源、GOV 锁定四态判定口径版本 R-2026.07)。 + +## 九、页脚 + +「数据来源:P-08-芯片主数据台账-模拟数据V0.1.xlsx · P-06-样品验证记录台账-模拟数据V0.1.xlsx · 字段口径:Demo台账字段细化与统一方案」——即本文档第一节的三个来源。 + +--- + +## 附:取值原则 + +1. **能直读不推导**:P-08 / P-06 有数据文件的字段全部逐字段直读,不改语义(仅做显示层截短/排版,如上各表「备注」列注明处)。 +2. **推导必标注**:P-01 / P-10 / P-11 无数据文件,看板右上角计数器与页头图例均以琥珀色标注「回链推导」;推导链条在本文档中逐字段写明。 +3. **不虚构事实类数据**:P-06 属事实证据类,凡页面出现的验证结论、精度数值、报告编号均可回溯到 P-06 记录原文,无凭空编造的测试数据。 diff --git a/docs/ss02-act-05/ACT-WORK-05-关键台账数据.html b/docs/ss02-act-05/ACT-WORK-05-关键台账数据.html new file mode 100644 index 0000000..581e299 --- /dev/null +++ b/docs/ss02-act-05/ACT-WORK-05-关键台账数据.html @@ -0,0 +1,409 @@ + + + + + +ACT-WORK-05 差异评估 · 关键台账数据 · SS02 需求分析与方案匹配 + + + +
+ +
+
SS02 需求分析与方案匹配 · 活动台账视图
+

ACT-WORK-05关键台账数据

+

视角:本活动一次运行实例的读数快照 · 关联需求 REQ-20260826-001(客户 CUS-2025-0007 · 项目 PRJ-PSU-40V)· 评估基准 LM2596S-5.0(TI) → 候选 XL2596S-5.0(XLSEMI)、LM2596S-ADJ(TI) · 规则版本 R-2026.07

+
+ 台账数据文件直读(P-08 / P-06 模拟数据 V0.1) + 关联回链推导(P-01 / P-10 / P-11 暂无独立数据文件,由 P-08 / P-06 的引用关系与参数集推导展示) +
+
+ + +
+
P-08 芯片主数据核心输入 · 评估原材料,查无即硬阻塞
+
P-11 替代批准关系库核心产出 · 结论与明细的落账地
+
P-10 原厂技术资料库证据链 · 每项结论的版本化出处
+
P-06 样品验证记录闭环去向 · UNKNOWN 的实测收口
+
P-01 客户需求主台账上下文 · 需求源头与回写对象
+
+ + +
+
+

P-01客户需求主台账

+ 上下文 · R+W + 启动时新建需求;结束时回写风险 / 验证要求 / 输出链接 / 下一步 / 当前阶段 + 回链推导 · 1 条 +
+
+
需求编号
REQ-20260826-001
+
客户
CUS-2025-0007
+
项目
PRJ-PSU-40V
+
需求版本
V1(启动新建)
+
需求描述
工业电源 12V→5V 电源位替代:原 LM2596S-5.0(NRND,PCN-2025-118 通知 2027-03 停产)
+
应用场景
12V/24V/40V 输入三档 · 0.5A~3A 负载 · 工业温域
+
BOM 位
U3 电源位
+
原内部料号
LM2596S-5.0
+
候选内部料号
XL2596S-5.0 · LM2596S-ADJ
+
风险等级(回写)
中
+
验证要求(回写)
VAL-2026-0188
+
当前阶段
提交 ACT-GATE-03
+
下一步动作
提交 ACT-GATE-03 评审放行(FAE · 李工 · 1 个工作日)
+
+
+ + +
+
+

P-08芯片主数据台账

+ 核心输入 · R(查无 → 数据工单创建) + 原 PN 与候选 PN 的物料基准 + 标准化参数集,逐项比对的原材料 + 数据文件直读 · 3 条(本活动相关) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
内部料号品牌 / 制造商规格描述封装 / 工艺生命周期Datasheet标准化参数集(8 项)适配场景
LM2596S-5.0
P08-0001 · v3
TI
Texas Instruments
降压型 DCDC 稳压器,3A 输出,固定 5.0V,150kHzTO-263-5
BCD
NRND
PCN-2025-118 已通知,2027-03 停止供货
Rev H(2023-09)
输入 4.5~40V输出 5.0V 固定电流 3A150kHz静态 5mA-40~+85℃EN 阈值 1.4V内置反馈
工业电源;板载 5V 轨
XL2596S-5.0
P08-0002 · v2
XLSEMI
XLSEMI
降压型 DCDC 稳压器,3A 输出,固定 5.0V,150kHz,与 LM2596S-5.0 引脚兼容TO-263-5
BCD
ActiveRev C(2025-06)
输入 4.5~40V输出 5.0V 固定电流 3A150kHz静态 4.2mA-40~+125℃EN 阈值 未披露内置反馈
工业电源;消费电源
LM2596S-ADJ
P08-0003 · v2
TI
Texas Instruments
降压型 DCDC 稳压器,3A,可调输出 1.23~37V,150kHzTO-263-5
BCD
ActiveRev H(2023-09)
输入 4.5~40V输出 1.23~37V 可调电流 3A150kHz静态 5mA-40~+125℃EN 阈值 1.4V外部分压反馈
可调电源;工控
+
+
+ + +
+
+

P-11替代选型与批准关系库 · 替代关系主记录

+ 核心产出 · R+W + 每个候选一条关系记录;客户批准状态为只读边界,本活动不写 + 回链推导 · 2 条 +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
替代关系 ID原 → 候选替代类型推荐等级分析状态风险验证要求 / 回链规则版本客户批准(只读)
P11-SUB-0001LM2596S-5.0 → XL2596S-5.0Pin-to-Pin条件推荐FAE确认中VAL-2026-0188(使能阈值实测,进行中)R-2026.07未提交
P11-SUB-0002LM2596S-5.0 → LM2596S-ADJ功能替代备选FAE确认低VAL-2026-0040(条件通过:外部分压精度 ±2.1%)R-2026.07未提交
+
+ +
+

P-11差异明细子表(非 MATCH 项,由双方 P-08 标准化参数集逐项比对得出)

+ 6 条 +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
所属关系参数项原值 → 候选值约束结论风险证据引用影响说明
SUB-0001生命周期NRND(PCN-2025-118)→ Active软约束MISMATCH高P-08 · P08-0001/0002原料 2027-03 停产——本次替代的驱动因素
SUB-0001工作温度-40~+85℃ → -40~+125℃硬约束MISMATCH中P-08 参数集 v2026-08候选温域更宽,覆盖原需求
SUB-0001使能阈值1.4V → 未披露硬约束UNKNOWN中挂 VAL-2026-0188原厂未披露,实测闭环前维持条件推荐
SUB-0001静态电流5mA → 4.2mA软约束MISMATCH低P-08 参数集 v2026-08候选更优,无不利影响
SUB-0002输出电压5.0V 固定 → 1.23~37V 可调硬约束CONDITIONAL中VAL-2026-0040需外部分压设定 5.0V,精度 ±2.1%(固定版 ±1%)
SUB-0002反馈网络内置固定分压 → 外部分压电阻硬约束CONDITIONAL中VAL-2026-0040BOM 增加 2 颗 0.1% 电阻,成本上升
+
+
+ + +
+
+

P-10原厂技术资料库

+ 证据链 · R(查无 → 数据工单创建) + 每项结论的 datasheet 出处,版本回链 + 回链推导 · 2 条(来自 P-08 datasheet_ref) +
+
+ + + + + + + + + + + + + + + + + + + + +
关联料号品牌资料类型资料文件版本状态
LM2596S-5.0 · LM2596S-ADJTIDatasheetti.com/lit/ds/snvsbt0/snvsbt0.pdfRev H(2023-09)有效
XL2596S-5.0XLSEMIDatasheetxlsemi.com/datasheet/XL2596S-5.0.pdfRev C(2025-06)有效
+
+
+ + +
+
+

P-06样品交付与芯片验证记录台账

+ 闭环去向 · R + 条件写(不可创建,只能实测产生) + UNKNOWN / CONDITIONAL 的收口处;验证完成后回链 P-11 + 数据文件直读 · 3 条(本活动相关) +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
验证 ID关联需求 / 项目验证料号验证类型测试条件结果 / 状态问题与风险后续动作
VAL-2026-0188REQ-20260826-001
PRJ-PSU-40V
XL2596S-5.0Pin-to-Pin样品上板:U3 电源位,输入 12V/24V/40V 三档,负载 0.5A/1.5A/3A,25℃进行中
待实测,排期 2026-08-28 · 批次 XL-202607-03
使能阈值差异影响开关机逻辑判定,风险=中完成 EN 阈值实测并回链 P11-SUB-0001;未闭环前维持条件推荐
VAL-2026-0040REQ-20260205-001
—
LM2596S-ADJ功能可调输出验证:外部分压设定 5.0V,与固定版输出精度对比条件通过
精度 ±2.1%(固定版 ±1%),温漂 +0.6% · RPT-2026-0040
精度敏感场景需 0.1% 电阻,BOM 成本上升,风险=低作为备选结论回链 P11-SUB-0002
VAL-2026-0032REQ-20260120-004
PRJ-PSU-40V
XL2596S-5.0可靠性高温满载:70℃ 环境、40V 输入、5V/3A 输出连续 96h通过
壳温 108℃(加散热片),输出漂移 +0.8% · RPT-2026-0032
——(作为本次评估历史证据引用)
+
+
+ + +
+
+

次要台账(只读引用,不进主看板)

+
+
+
P-09客户技术与机会档案提供 BOM 位(U3 电源位)与客户技术栈背景;缺口由执行工单补齐。
+
EXT-02外部来源清单登记本次评估用到的可信外部来源(原厂官网 datasheet 等)。
+
GOV评估规则库四态判定口径;本次锁定版本 R-2026.07,随结论一并落账。
+
+
+ + + +
+ + diff --git a/tools/e2e-a2.mjs b/tools/e2e-a2.mjs index 4507527..12c6684 100644 --- a/tools/e2e-a2.mjs +++ b/tools/e2e-a2.mjs @@ -42,6 +42,13 @@ const E2E_CLIENT_SECRET = process.env.KEYCLOAK_E2E_CLIENT_SECRET ?? ''; * 不设它才走服务账号那条正路(ADR-012)。 */ const DIRECT_BEARER = (process.env.JCAI_E2E_BEARER ?? '').replace(/^Bearer\s+/i, '').trim(); +/** + * **要不要落真库**(`JCAI_E2E_LAND_MASTER=1`)—— 默认关:起单接口默认开着(人手点那条路要看见数据真变), + * 而 e2e 关它是为了保护「只能测一次」的样例(A1 新客户来函 / W05 候选缺库:主数据一旦真建进去, + * 第二次跑那条路就成了老客户 / 都在库)。开着跑一次能验「数据工单确认即入库」真的落到了正式表 —— + * 跑完记得把建进去的料号清掉,不然 W05 案例 0/2 从此测不出缺库那条路。 + */ +const LAND_MASTER = process.env.JCAI_E2E_LAND_MASTER === '1'; /** access token 只活 5 分钟:超过 4 分钟就换一枚,免得长案例中途 401 */ const TOKEN_REFRESH_MS = 4 * 60 * 1000; @@ -553,6 +560,8 @@ const SUITES = { rowKey: 'internal_pn', blankCol: 'brand_name', expectGateBlock: true, + // 数据工单确认即入库(2026-08-27 用户定)—— 交卷那一刻这张卡的表就该进正式表 + landsOnConfirm: true, edits: [], // 案例没点名的料号兜底(不该走到这里 —— 每个案例都在 stageEdits 里逐颗给了) fillAllBlank: { @@ -580,6 +589,7 @@ const SUITES = { rowKey: 'internal_pn', blankCol: 'document_title', expectGateBlock: true, + landsOnConfirm: true, edits: [], fillAllBlank: { doc_seq: '01', @@ -838,6 +848,27 @@ function draftRows(drafts, address) { return (drafts ?? []).filter((d) => d.address === address).map((d) => ({ ...d.key, ...d.patch })); } +/** + * **数据工单确认即入库**(2026-08-27 用户定:「数据工单里填的数据,确认后就应该入库」)—— + * 交卷那一刻就验,不等放行: + * · 痕上必须有一条「数据工单确认入库 / 已确认(试跑」(基座在回传路由里同步落的,交卷返回时已在); + * · 落真库时(`JCAI_E2E_LAND_MASTER=1`)正式表里必须查得到件上填的那几颗料; + * · 不落真库时痕必须如实写「只留在活动缓存」—— 界面不撒谎那一条也要验。 + * 回 'landed' / 'held' / 'missing'(没这条痕 = 这道门没开)/ 'absent'(说落了、正式表里没有)。 + */ +async function expectLandedOnConfirm(taskId, stage, rows) { + const { body } = await api(`/api/gov/testbed/activity/${taskId}`); + const note = [...(body.trail ?? [])].reverse().find((t) => t.kind === 'ledger-write' && /数据工单(确认入库|已确认)/.test(t.title ?? '')); + if (note === undefined) return 'missing'; + if (/试跑|未落正式表/.test(note.title)) return LAND_MASTER ? 'absent' : 'held'; + if (!LAND_MASTER) return 'landed'; + const [domain, object] = stage.address.split('.'); + const formal = await api(`/api/gov/data/${domain}/${object}`); + const keys = Array.isArray(stage.rowKey) ? stage.rowKey : [stage.rowKey]; + const present = (row) => (formal.body.rows ?? []).some((f) => keys.every((k) => String(f[k] ?? '') === String(row[k] ?? ''))); + return rows.every(present) ? 'landed' : 'absent'; +} + /** 交卷执行卡 —— 载荷零字段(执行卡上没有任何输入,填写全在件上) */ async function submitExecution(cardId) { await api('/api/desk/work-orders/reply', { @@ -1135,7 +1166,7 @@ async function runCase(suite, kase) { data: sample.input, message: sample.message ?? '', ownerId: '', - landMaster: false, + landMaster: LAND_MASTER, }), }); const taskId = started.taskId; @@ -1147,6 +1178,8 @@ async function runCase(suite, kase) { // 录入卡的已答名单单独记 —— 与 decided 共用会把 decideSeq 按 size 取的序号顶掉(实测:点名驳回被跳过) const entered = new Set(); let gateBlocked = false; + /** 数据工单确认了却没入库(或说入库了而表里没有)—— 收工判失败 */ + let landingBroken = false; /** 资料文件那一次检查的结论(在办中验,见下面那段注释) */ let fileCheck; let gateLeaked = false; @@ -1331,6 +1364,19 @@ async function runCase(suite, kase) { } console.log(` 交卷执行卡:${card.title}`); replied.add(card.id); + if (stage.landsOnConfirm === true) { + const verdict = await expectLandedOnConfirm(taskId, stage, tables[stage.address] ?? []); + console.log( + verdict === 'landed' + ? ` 数据工单确认即入库:${stage.address} 已进正式表` + : verdict === 'held' + ? ` 数据工单已确认(试跑不落主数据,痕如实写「只留在活动缓存」)` + : verdict === 'missing' + ? ` ⚠️ 数据工单确认了,痕上没有「确认入库」那一条 —— 这道门没开,判失败` + : ` ⚠️ 痕说入库了,正式表里查不到件上填的那几行 —— 判失败`, + ); + if (verdict === 'missing' || verdict === 'absent') landingBroken = true; + } } else if (card.mode === 'acceptance') { if (kase.acceptEdits && !acceptEdited) { acceptEdited = true; @@ -1379,6 +1425,10 @@ async function runCase(suite, kase) { console.log(' ⚠️ 提交闸没拦住空手交卷 —— 判失败'); ok = false; } + if (landingBroken) { + console.log(' ⚠️ 数据工单确认即入库那道门没兑现 —— 判失败'); + ok = false; + } // 「超限才叫人」是 A4 的要害:该叫的那一单没叫,等于底价门禁是摆设 if (missedRows.size > 0) { console.log(` ⚠️ 案例点名要填的行,整例跑完也没在件上出现过(${missedRows.size} 处:${[...missedRows].slice(0, 3).join('、')}…)—— 判失败`);