W05 在途修订:差异评估结论审核件一个候选一张表 · 关键台账数据看板件(ledger-board)· S1 清单不再把人刚填的参数摆成缺口

活动包 `part-substitution-assessment`(case-model / manifest / report / target-style / badge / ledger-board 与两份包内测试)、
目标件「验收工单-01-差异评估结论审核」、`docs/ss02-act-05` 关键台账数据两份、端到端测试记录、e2e 脚本。
逐条口径见同批 CLAUDE.md 快照。2026-08-27 另一会话做的,一直留在工作树没入库;与同批另两条提交互相依赖。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0167qWjeyBwvfdGovd7FWbCu
This commit is contained in:
xcs
2026-08-27 23:20:15 +08:00
co-authored by Claude Fable 5
parent 0923fbfe4d
commit 909b1fe4b7
13 changed files with 1943 additions and 208 deletions
@@ -13,8 +13,14 @@ import { manifest, D, RULE_VERSION } from '../src/manifest.ts';
import activity from '../src/index.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 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']; 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 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 ?? ''; const viewHtml = (name: string) => manifest.assets.viewTemplates.find((t) => t.name === name)?.html ?? '';
@@ -67,8 +73,8 @@ describe('清单本身合规', () => {
expect(newCustomer).toBeDefined(); expect(newCustomer).toBeDefined();
}); });
it('五张视图模版都声明了,剧本每一步引用的 view 都在其中', () => { it('五张工单视图 + 一张运营侧看板都声明了,剧本每一步引用的 view 都在其中', () => {
expect(new Set(manifest.assets.viewTemplates.map((v) => v.name))).toEqual(new Set(VIEWS)); 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 }>) { for (const step of manifest.assets.businessModel.steps as ReadonlyArray<{ view?: string }>) {
if (step.view !== undefined) expect(VIEWS, step.view).toContain(step.view); 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); 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 }>) { 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(); if (NO_DONE_WHEN.has(s.item)) expect(s.doneWhen, s.item).toBeUndefined();
else expect(s.doneWhen, s.item).toBeDefined(); 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: 'analysis_status' },
{ view: 'landing-confirm', domain: 'substitution', object: 'relation', column: 'risk_level' }, { view: 'landing-confirm', domain: 'substitution', object: 'relation', column: 'risk_level' },
{ view: 'landing-confirm', domain: 'demand', object: 'request', column: 'request_status' }, { 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-<token> 规则(选择器是从值算出来的,不是手写的)', () => { it('每张模版摆了徽标的每个 tally 列,声明里的每个值都有对应的 v-<token> 规则(选择器是从值算出来的,不是手写的)', () => {
@@ -400,3 +423,134 @@ const CONTRACT = {
prohibitions: [], prohibitions: [],
discretion: [], discretion: [],
} as const; } 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 逐个复制 <section class="cand">,里层明细只列该候选名下的行(data-by 父键过滤);
* 汇总表一个候选一条;品牌从 parts.profile 整表按料号挑(取值,不是算)。
*/
describe('difference-review:一个候选一张表', () => {
const html = viewHtml('difference-review');
it('候选块是嵌套行模版:外层 relation、里层明细按 substitution_relation_id 过滤;容器标签不在行模版里再出现', () => {
expect(html).toContain('<article class="cands" data-rows="mine:substitution.relation"><section class="cand">');
expect(html).toContain('data-rows="mine:substitution.difference_item" data-by="substitution_relation_id"');
const inner = html.slice(html.indexOf('<section class="cand">'), html.indexOf('</section></article>'));
expect(inner).not.toContain('<article');
expect(inner).toContain('class="cand-idx"');
expect(inner).toContain('data-slot="original_internal_pn"');
});
it('汇总表一个候选一条:推荐程度 / 风险级别按值上色,差异统计绑 difference_summary,品牌按料号从 parts.profile 挑', () => {
expect(html).toContain('<tbody data-rows="mine:substitution.relation"><tr>');
expect(html).toContain('<span class="rec" data-class-of="recommendation_level"></span>');
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(`<article class="prefs" data-rows="mine:${D}.pn_check"><section class="pref">`);
expect(html).toContain('<tbody data-rows="parts.parameter" data-by="internal_pn"><tr>');
const block = html.slice(html.indexOf('<article class="prefs"'), html.indexOf('</section></article>'));
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<string>();
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('<span class="pnrow" data-rows="sub_eval.pn_check" data-by="internal_pn">');
expect(html).toContain('<div class="pchips" data-rows="parts.parameter" data-by="internal_pn">');
expect(html).toContain('<tbody class="hist" data-rows="validation.history">');
expect(html).not.toContain('mine:');
});
it('无脚本、无 on*、无外链(过安装静态审);有 </body> 给出口注入报高脚本;三态由 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('</body>');
expect(html).toContain('<div class="page" data-class-of="board:state">');
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 ['<th>内部料号</th>', '<th>替代关系 ID</th>', '<th>参数项</th>', '<th>关联料号</th>', '<th>验证 ID</th>', '<th>标准化参数集</th>']) 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('<tr data-class-of="difference_status">');
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"');
});
});
@@ -174,6 +174,45 @@ describe('说明书与剧本的骨架', () => {
expect(secondPass).toMatch(/收不了步|收不掉/); 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 的一颗都不建', () => { it('S0 剧本写明都在库时数据工单两项本就不会进「当下可为」;S0d_p08_master 剧本写明 found 的一颗都不建', () => {
const s0 = ASSESSMENT_STEPS.find((s) => s.item === 'S0_bind')?.guide ?? ''; const s0 = ASSESSMENT_STEPS.find((s) => s.item === 'S0_bind')?.guide ?? '';
expect(s0).toContain('当下可为'); expect(s0).toContain('当下可为');
@@ -206,23 +245,47 @@ describe('说明书与剧本的骨架', () => {
for (const step of ASSESSMENT_STEPS) expect(step.guide, step.item).not.toMatch(disableCallPattern); 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 p08 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master');
const p10 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p10_document'); const p10 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p10_document');
expect(p08?.view).toBe('p08-master-create'); expect(p08?.view).toBe('p08-master-create');
expect(p10?.view).toBe('p10-document-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 = /<sentry id="s_pns_resolved">[\s\S]*?<condition>([\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 ?? ''; const s0d = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master')?.guide ?? '';
expect(s0d).toContain('finish S0d_p08_master'); expect(s0d).toContain('finish S0d_p08_master');
expect(s0d).toMatch(/顺序不能反/); expect(s0d).toMatch(/引擎也会自己收/);
expect(s0d).toContain('p08-master-create'); expect(s0d).not.toMatch(/顺序不能反/);
const s0d10 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p10_document')?.guide ?? ''; const s0d10 = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p10_document')?.guide ?? '';
expect(s0d10).toContain('finish S0d_p08_master'); expect(s0d10).toMatch(/引擎按入库事实自己收/);
expect(s0d10).toMatch(/若它还开着|先.*finish/);
}); });
it('「发卡前清单必须先有行」写在说明书里,不只在剧本里(2026-08-27 实测:模型把 P-10 收步与 S1 发卡压成一拍,那一拍 S1 剧本没摊开,卡发出去时 input_gap 一行都没有,人对着空表无从下手)', () => { it('「发卡前清单必须先有行」写在说明书里,不只在剧本里(2026-08-27 实测:模型把 P-10 收步与 S1 发卡压成一拍,那一拍 S1 剧本没摊开,卡发出去时 input_gap 一行都没有,人对着空表无从下手)', () => {
@@ -270,7 +333,7 @@ describe('说明书与剧本的骨架', () => {
expect(NARRATIVE).toContain('parts.document` 必须先有骨架行'); expect(NARRATIVE).toContain('parts.document` 必须先有骨架行');
expect(NARRATIVE).toMatch(/压拍/); expect(NARRATIVE).toMatch(/压拍/);
// 标了几颗建好,主数据就得有几行 —— 逐颗对账(第一刀写成「≥1 行就算数」太松,三候选那单从缝里过去了) // 标了几颗建好,主数据就得有几行 —— 逐颗对账(第一刀写成「≥1 行就算数」太松,三候选那单从缝里过去了)
expect(CMMN_XML).toContain('parts_profile_rows &gt;= sub_eval_pn_check_exists_status_created'); expect(CMMN_XML).toContain('parts_profile_rows &gt;= sub_eval_pn_check_exists_status_absent + sub_eval_pn_check_exists_status_created');
}); });
it('评估输入那张卡的**前提**写在说明书的卡骨架上(2026-08-27 e2e:两颗缺库,模型只问只建了第一颗,CN 还 absent 就把 S1 的卡发了出去 —— 发卡的授权面是调人预算,绕得过哨兵)', () => { 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 ?? ''; const g = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master')?.guide ?? '';
expect(g).toMatch(/参数名一律用中文/); 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).toContain('input_voltage_range');
expect(g).toMatch(/不是同一行/); expect(g).toMatch(/不是同一行/);
}); });
@@ -316,6 +379,7 @@ describe('说明书与剧本的骨架', () => {
it('件上三处样式毛病钉死(2026-08-27 用户:表显示不全不能右滑 / 编辑态内部料号只读字与输入框并排 / input 与 select 没对齐)', () => { it('件上三处样式毛病钉死(2026-08-27 用户:表显示不全不能右滑 / 编辑态内部料号只读字与输入框并排 / input 与 select 没对齐)', () => {
const html = manifest.assets.viewTemplates.find((v) => v.name === 'p08-master-create')?.html ?? ''; const html = manifest.assets.viewTemplates.find((v) => v.name === 'p08-master-create')?.html ?? '';
// 两种包装都要能横滑:验收件用目标件的 .tbl-wrap(它的验收版写着 overflow:hidden),2026-08-27 用户在结论审核页撞上 // 两种包装都要能横滑:验收件用目标件的 .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;'); 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('#tbl1.on td > .jc-vcell:not(.ro) { display: none; }');
expect(html).toContain('input.jc-cell, select.jc-cell { height: 32px;'); 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()); 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');
});
});
@@ -94,7 +94,12 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
却只建了第一颗,一行足以让门开着,直到 S2 写关系时才被引用检查拦下。 却只建了第一颗,一行足以让门开着,直到 S2 写关系时才被引用检查拦下。
改成**逐颗对账**:\`parts_profile_rows >= created\` —— 标了几颗建好,就得有几行。 改成**逐颗对账**:\`parts_profile_rows >= created\` —— 标了几颗建好,就得有几行。
--> -->
<ifPart id="if_pr"><condition>sub_eval_pn_check_rows = count(candidateInternalPns) + 1 and sub_eval_pn_check_missing = 0 and sub_eval_pn_check_exists_status_absent = 0 and sub_eval_pn_check_exists_status_abandoned &lt; count(candidateInternalPns) and parts_profile_missing = 0 and parts_parameter_missing = 0 and parts_document_missing = 0 and parts_profile_rows &gt;= sub_eval_pn_check_exists_status_created and parts_document_rows &gt;= sub_eval_pn_check_exists_status_created and parts_parameter_rows = parts_parameter_distinct_param_name * sub_eval_pn_check_exists_status_created</condition></ifPart> <!--
**不再等模型把 absent 改成 created**(2026-08-27 晚):那面旗是模型记的账,忘了记就永远开不了门
(「机检只读基座数」的哨兵形)。「一颗 absent 都不剩」的基座事实 = 要建的每一颗(absent + created)
主数据、参数、资料都落了且必填不缺;created 仍可写(说明书照旧让它记),但门不靠它。
-->
<ifPart id="if_pr"><condition>sub_eval_pn_check_rows = count(candidateInternalPns) + 1 and sub_eval_pn_check_missing = 0 and sub_eval_pn_check_exists_status_abandoned &lt; count(candidateInternalPns) and parts_profile_missing = 0 and parts_parameter_missing = 0 and parts_document_missing = 0 and parts_profile_rows &gt;= sub_eval_pn_check_exists_status_absent + sub_eval_pn_check_exists_status_created and parts_document_rows &gt;= 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)</condition></ifPart>
</sentry> </sentry>
<!-- ③ 评估输入已确认(旗 + 清单有行且必填齐)—— 标准化那一步的入场前提 --> <!-- ③ 评估输入已确认(旗 + 清单有行且必填齐)—— 标准化那一步的入场前提 -->
<sentry id="s_input_confirmed"> <sentry id="s_input_confirmed">
@@ -130,13 +135,13 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<!-- 启动期:铸案号、建需求 V1、锁规则版本、逐个料号查库(能算的不问人) --> <!-- 启动期:铸案号、建需求 V1、锁规则版本、逐个料号查库(能算的不问人) -->
<humanTask id="S0_bind" name="建立评估上下文:绑定四项、新建需求、存在性核对"/> <humanTask id="S0_bind" name="建立评估上下文:绑定四项、新建需求、存在性核对"/>
<!-- 0.5a 等待数据(建主数据):P-08 数据工单;触达人,不给 doneWhen;都在库时 s_bound 不开,不进「当下可为」 --> <!-- 0.5a 等待数据(建主数据):P-08 数据工单;人工激活,doneWhen 只在人答过卡之后求(入库事实);都在库时 s_bound 不开,不进「当下可为」 -->
<humanTask id="S0d_p08_master" name="数据工单:补建芯片主数据(P-08)"> <humanTask id="S0d_p08_master" name="数据工单:补建芯片主数据(P-08)">
<entryCriterion id="ec_s0d8" sentryRef="s_bound"/> <entryCriterion id="ec_s0d8" sentryRef="s_bound"/>
<itemControl><manualActivationRule id="m_s0d8"/></itemControl> <itemControl><manualActivationRule id="m_s0d8"/></itemControl>
</humanTask> </humanTask>
<!-- 0.5b 等待数据(建资料):P-10 数据工单;触达人,不给 doneWhen;都在库时 s_p08_created 不开,不进「当下可为」 --> <!-- 0.5b 等待数据(建资料):P-10 数据工单;人工激活,doneWhen 只在人答过卡之后求(入库事实);都在库时 s_p08_created 不开,不进「当下可为」 -->
<humanTask id="S0d_p10_document" name="数据工单:补建原厂技术资料(P-10)"> <humanTask id="S0d_p10_document" name="数据工单:补建原厂技术资料(P-10)">
<entryCriterion id="ec_s0d10" sentryRef="s_p08_created"/> <entryCriterion id="ec_s0d10" sentryRef="s_p08_created"/>
<itemControl><manualActivationRule id="m_s0d10"/></itemControl> <itemControl><manualActivationRule id="m_s0d10"/></itemControl>
@@ -256,7 +261,7 @@ key 是 {internal_pn, doc_seq:"01"});一行都没有的话人打开是空表、
"ask":"请把这些料号的 datasheet / 承诺函等资料记录补进原厂技术资料库(一份资料一行,可加行)。", "ask":"请把这些料号的 datasheet / 承诺函等资料记录补进原厂技术资料库(一份资料一行,可加行)。",
"steps":["资料类型、标题、版本、文件链接、资料状态为必填;证据定位写页码或章节,差异评估的证据引用会指向它", "steps":["资料类型、标题、版本、文件链接、资料状态为必填;证据定位写页码或章节,差异评估的证据引用会指向它",
"同一料号多份资料就多加几行,序号 01/02… 自己续", "同一料号多份资料就多加几行,序号 01/02… 自己续",
"改完保存,回这张卡确认"], "改完保存,回这张卡确认 —— 确认后这批资料记录即入库,活动接着往下办"],
"standard":"每颗料号至少一行 datasheet 记录,必填列全齐。", "standard":"每颗料号至少一行 datasheet 记录,必填列全齐。",
"gateLedgers":["parts.document"], "gateLedgers":["parts.document"],
"basis":[{"k":"触发原因","v":"按 internal_pn 查 P-10 查无记录"}]}} "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":{ {"kind":"human","itemId":"S1_input_confirm","workOrder":"execution","card":{
"gateLedgers":["${D}.input_gap"], "gateLedgers":["${D}.input_gap"],
"title":"评估输入与资料补齐确认:<原 PN> → <N> 个候选", "title":"评估输入与资料补齐确认:<原 PN> → <N> 个候选",
"ask":"启动绑定的四项已锁定。请在工作区的件上补齐「待补 / 待确认」行的补充内容,默认值可改可确认。", "ask":"启动绑定的四项已锁定。请在工作区的件上补齐「待补」行的补充内容,默认值可改可确认。",
"steps":["应用场景、BOM 位是启动时未收的:必须补(写在「补充内容」列)", "steps":["应用场景、BOM 位是启动时未收的:必须补(写在「补充内容」列)",
"原厂未披露的参数(如使能阈值)补不到就写「挂验证任务」或「留 UNKNOWN」—— 补不了也是合法答案,不拦流程", "每颗料每个参数此刻的值摆在件上的「候选参数现值」表里(只读参考);原厂未披露的参数这里不用补 —— 差异评估会把它判成 UNKNOWN 并挂验证任务",
"替代类型 / 规则版本 / 负责人期限三行是系统默认,可改可不改", "替代类型 / 规则版本 / 负责人期限三行是系统默认,可改可不改",
"加一行 = 补充 AI 没列出的资料项;删一行 = 该项不适用;已绑定的四项不可改", "加一行 = 补充 AI 没列出的资料项;删一行 = 该项不适用;已绑定的四项不可改",
"改完保存,回这张卡提交"], "改完保存,回这张卡提交"],
"standard":"每一条「待补」行的补充内容都有;补不了的写明理由。", "standard":"每一条「待补」行的补充内容都有;补不了的写明理由。",
"basis":[{"k":"已绑定","v":"客户 <customerCode> · 需求 <request_id> V1 · 原 PN <原 PN> · 候选 <N> 颗"}, "basis":[{"k":"已绑定","v":"客户 <customerCode> · 需求 <request_id> V1 · 原 PN <原 PN> · 候选 <N> 颗"},
{"k":"缺口","v":"<N> 项待补 / 待确认"}]}} {"k":"缺口","v":"<N> 项待补"}]}}
⑥ 验收工单 01 · 差异评估结论审核(S4_review;决策卡循环,不是终局验收)—— 发之前先在轨迹里找有没有已答过、还没处理的 \`[card-reply]\`: ⑥ 验收工单 01 · 差异评估结论审核(S4_review;决策卡循环,不是终局验收)—— 发之前先在轨迹里找有没有已答过、还没处理的 \`[card-reply]\`:
· \`value\` 是 \`"approve"\` → 写 \`${D}.verdict.review_approved_total = 1\`,finish 这一项,别再发卡; · \`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'], reads: [`${D}.pn_check`, 'candidateInternalPns', 'originalInternalPn', 'datasheets', 'parts.profile', 'parts.parameter', 'parts.document'],
view: 'p08-master-create', view: 'p08-master-create',
tools: [], 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 都不会进你的「当下可为」。**先分岔,再动作**: guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走 —— 都在库时入场前提本就不成立,这一项与下一项 S0d_p10_document 都不会进你的「当下可为」。**先分岔,再动作**:
【名单】= \`${D}.pn_check\` 里 **exists_status = "absent" 的每一行**,不管它是原 PN 还是候选 —— **一律建,不问人** 【名单】= \`${D}.pn_check\` 里 **exists_status = "absent" 的每一行**,不管它是原 PN 还是候选 —— **一律建,不问人**
@@ -381,13 +392,13 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不
没有 datasheet 就只写 internal_pn 这一列(其余留给人填,**别编**)。 没有 datasheet 就只写 internal_pn 这一列(其余留给人填,**别编**)。
2. 同一拍给每颗料写标准化参数集骨架(表 2,\`parts.parameter\`,一行一参数): 2. 同一拍给每颗料写标准化参数集骨架(表 2,\`parts.parameter\`,一行一参数):
**参数名一律用中文,与库里既有料号的那几行同名**(输入电压范围 / 输出电压 / 输出电流 / 开关频率 / **参数名一律用中文,与库里既有料号的那几行同名**(输入电压范围 / 输出电压 / 输出电流 / 开关频率 /
静态电流 / 工作温度 / 使能阈值…)—— \`param_name\` 是主键的一半,写成 \`input_voltage_range\` 静态电流 / 工作温度…)—— \`param_name\` 是主键的一半,写成 \`input_voltage_range\`
与写成「输入电压范围」**不是同一行**:原 PN 那边是中文、候选这边是英文,比对时两套名字对不上, 与写成「输入电压范围」**不是同一行**:原 PN 那边是中文、候选这边是英文,比对时两套名字对不上,
逐条判成 UNKNOWN,结论就废了(2026-08-27 实测:27 条里 14 条 UNKNOWN)。 逐条判成 UNKNOWN,结论就废了(2026-08-27 实测:27 条里 14 条 UNKNOWN)。
封装、工艺、生命周期这些**不是参数**,它们在表 1,别往表 2 里塞。 封装、工艺、生命周期这些**不是参数**,它们在表 1,别往表 2 里塞。
**建几颗料就用同一套名字**:门上有一条对账 —— 参数行数 = 不同参数名数 × 建过的料数, **建几颗料就用同一套名字**:门上有一条对账 —— 参数行数 = 不同参数名数 × 建过的料数,
只有每颗料的名字集合一模一样时才成立;混着写(这颗中文那颗英文、或同一颗写两套)这一步就过不去。有 \`datasheets[].text\` 就从里面读出典型参数 只有每颗料的名字集合一模一样时才成立;混着写(这颗中文那颗英文、或同一颗写两套)这一步就过不去。有 \`datasheets[].text\` 就从里面读出典型参数
(如输入电压范围 / 输出电压 / 输出电流 / 开关频率 / 静态电流 / 工作温度 / 使能阈值等,按品类而定)各写一行, (如输入电压范围 / 输出电压 / 输出电流 / 开关频率 / 静态电流 / 工作温度等,按品类而定)各写一行,
key {internal_pn, param_name},patch 带 param_value / param_unit / constraint_type(hard/soft)/ param_note(一句话说清这项为什么要卡, key {internal_pn, param_name},patch 带 param_value / param_unit / constraint_type(hard/soft)/ param_note(一句话说清这项为什么要卡,
如「须覆盖客户输入上限」);原厂没写的值写「未披露」(它会进入 UNKNOWN 并挂验证任务)。没有 datasheet 就只写 如「须覆盖客户输入上限」);原厂没写的值写「未披露」(它会进入 UNKNOWN 并挂验证任务)。没有 datasheet 就只写
internal_pn + param_name 骨架(把你判断这颗料该评估的参数名列出来,**param_value 留空给人填,别编**)。 internal_pn + param_name 骨架(把你判断这颗料该评估的参数名列出来,**param_value 留空给人填,别编**)。
@@ -410,14 +421,13 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不
而那颗料从此没人建,后面写替代关系时被引用检查逐颗拦下(2026-08-27 实测,一单连拒四次)。 而那颗料从此没人建,后面写替代关系时被引用检查逐颗拦下(2026-08-27 实测,一单连拒四次)。
· **一颗都不剩了** → 才走下面这两步。 · **一颗都不剩了** → 才走下面这两步。
这一拍的动作串**恰好两个,顺序不能反**: 这一拍的动作串:① \`finish S0d_p08_master\`(建好的料主数据与参数都已入库、必填不缺时引擎也会自己收这一项 ——
① 先 \`finish S0d_p08_master\` —— **必须先把这一项收掉**。不收掉,引擎挑工作区画面时按 你 finish 一下更省一拍,忘了也不会卡住);② 照说明书④发 P-10 数据工单 —— 下一项是 S0d_p10_document,同一批料号接着补资料记录。
「当下还没干完的第一步」取 view,S0d_p08_master 只要还 Active,画面就一直停在
\`p08-master-create\`(P-08 那两张表);下一拍就算发出了 P-10 的卡,人的工作区也看不见
P-10 的资料表,一格都填不了,提交闸必然拦死。
② 再照说明书④发 P-10 数据工单 —— 下一项是 S0d_p10_document,同一批料号接着补资料记录。
【这张卡一单只发一次】人交回来之后不许再发;答复里没有值(空 values)也算答过 —— 人在件上填的东西已经落草稿层。`, 【这张卡一单只发一次】人交回来之后不许再发;答复里没有值(空 values)也算答过 —— 人在件上填的东西已随确认**入了正式表**
(轨迹里有一条「数据工单确认入库」的痕点名哪几张表各几行;试跑单不落主数据时那条痕会如实写「只留在活动缓存」)。
这几颗料从此在库:**别再往 \`parts.profile\` / \`parts.parameter\` 写它们的键**(在库的键会被当场拒),也别去 drop 它们
(那是本单自己建好的行,撤不掉);值要改由人在件上改。`,
}, },
{ {
item: 'S0d_p10_document', item: 'S0d_p10_document',
@@ -428,19 +438,21 @@ P-10 的资料表,一格都填不了,提交闸必然拦死。
reads: [`${D}.pn_check`, 'parts.profile', 'parts.document'], reads: [`${D}.pn_check`, 'parts.profile', 'parts.document'],
view: 'p10-document-create', view: 'p10-document-create',
tools: [], tools: [],
/* 触达人,不给 doneWhen;人工激活:发卡就算启动 */ /* 人工激活:发卡就算启动。doneWhen 同 P-08 的道理:人答过卡之后,建过的每颗料都有一行资料记录且必填不缺 → 引擎自己收 */
guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走(要走到这一步,S0d_p08_master 必然已经收完);都在库时入场前提本就不成立, doneWhen: `parts_document_rows > 0 and parts_document_rows >= parts_profile_rows and parts_document_missing = 0`,
这一项与 S0d_p08_master 都不会进你的「当下可为」。**进到这一项时,S0d_p08_master 应当已经收掉了;若它还开着(Active), guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走(要走到这一步,S0d_p08_master 已经收完 —— 引擎按入库事实自己收);都在库时入场前提本就不成立,
先 \`finish S0d_p08_master\` 再发这张卡** —— 它没收掉,画面会一直停在 P-08 那两张表,人拿到 P-10 的卡也看不见资料表。 这一项与 S0d_p08_master 都不会进你的「当下可为」。
【发 P-10 数据工单】「建」的名单就是 \`${D}.pn_check\` 里仍是 exists_status = "absent" 的那几行(候选总数 ≥2 时被放弃的那些, 【发 P-10 数据工单】「建」的名单就是 \`${D}.pn_check\` 里仍是 exists_status = "absent" 的那几行(候选总数 ≥2 时被放弃的那些,
S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里)。同一拍照说明书④发那张 P-10 数据工单(title 里列全料号,只发一次), S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里)。同一拍照说明书④发那张 P-10 数据工单(title 里列全料号,只发一次),
发完这一拍结束(挂起)。 发完这一拍结束(挂起)。
【P-10 那张确认回来了】把名单里每颗料的 \`${D}.pn_check\` 改成 exists_status = "created"(同 PK 覆盖写),然后 finish 这一项。 【P-10 那张确认回来了】把名单里每颗料的 \`${D}.pn_check\` 改成 exists_status = "created"(同 PK 覆盖写,记账用),然后 finish 这一项
这时 S1_input_confirm 的入场前提(全部料号落定、本单建的主数据不缺必填)自己就成立了 —— 缺必填由提交闸兜着,卡回来时一定是齐的。 (资料记录都入库、必填不缺时引擎也会自己收)。S1_input_confirm 的入场前提认的是**入库事实**(要建的每颗料主数据 / 参数 / 资料都落了且不缺必填),
不等这面旗 —— 缺必填由提交闸兜着,卡回来时一定是齐的。
【这张卡一单只发一次】人交回来之后不许再发;答复里没有值(空 values)也算答过 —— 人在件上填的东西已经落草稿层。`, 【这张卡一单只发一次】人交回来之后不许再发;答复里没有值(空 values)也算答过 —— 人在件上填的东西已随确认**入了正式表**
(轨迹里有一条「数据工单确认入库」的痕)。这几份资料记录从此在库:**别再往 \`parts.document\` 写它们的键**(会被当场拒),也别去 drop 它们。`,
}, },
{ {
item: 'S1_input_confirm', item: 'S1_input_confirm',
@@ -453,14 +465,20 @@ S0d_p08_master 那一步已经把它们改成 "abandoned",不在这份名单里)
guide: `料号全部落定了,**该 PW 补评估输入了** —— 这是本活动唯一的执行工单,零输入。**先分岔**:【当前这一步】那行引擎事实说 guide: `料号全部落定了,**该 PW 补评估输入了** —— 这是本活动唯一的执行工单,零输入。**先分岔**:【当前这一步】那行引擎事实说
「从没给它发过卡」→ 走【发卡】;说「人已经被问过了」→ 走【收口】。 「从没给它发过卡」→ 走【发卡】;说「人已经被问过了」→ 走【收口】。
【发卡】先把评估输入清单写下来(\`${D}.input_gap\`,一行一项,item_key 用英文短键,input_item 用人读名),一拍连做: 【发卡】先把评估输入清单写下来(\`${D}.input_gap\`,一行一项),一拍连做。
· 已绑定四项(status = "ok",detail 写绑定值,origin = "ai"):item_key = customer / request / original_pn / candidates **每一行六列都要写,一列都不能少**:item_key(英文短键)/ input_item(人读名,就是下面每行「」里那几个字)/ source_ledger(来源台账)/
(候选多颗就 candidate_1、candidate_2… 一颗一行,detail 写料号与在库状态); 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 = "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 = "default",detail 写默认值,source_ledger = "GOV"):substitution_type「替代类型」= "Pin-to-Pin"、rule_version「规则版本」= "${RULE_VERSION}"、owner「负责人 / 期限」= "按客户线默认 · 1 个工作日";
· 待确认(status = "gap"):逐颗候选看它名下 \`parts.parameter\` 的那几行,param_value 是「未披露」的各一行(item_key = unknown_<料号>_<序号>, · **候选参数一行都不写**:原厂未披露的参数不是这份清单的事 —— 差异评估那一步(S3)会把它判成 UNKNOWN 并挂验证任务;
input_item 如「XL2596S-5.0 使能阈值」,source_ledger = "P-08",detail 写「原厂未披露(有而不全)」); 件上另有一张只读的「候选参数现值」表(P-08 标准化参数集,一颗料一张,基座从台账现读),人看得见每颗料每个参数此刻的值。
· 齐备项(status = "ok"):原 PN 资料版本(source_ledger = "P-10",detail 写 datasheet 版本)。 这份清单里**不许出现任何参数行**(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: 【收口】人交回之后只做三件事,然后 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 照第一拍出参**一字不改**; ① 逐条 \`substitution.difference_item\` 同 PK 覆盖写:difference_status / compare_result / risk_level 照第一拍出参**一字不改**;
impact_statement(MATCH 写「无」;其余说清对应用 / 验证 / 报价的影响,如「输入上限 32V < 客户 40V,不能上板」); 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(候选)、 ② 每个 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。 validation_type = "pin_to_pin"、validation_status = "not_started"、next_validation_action(要测什么、什么条件)、test_condition。
③ 逐条 \`substitution.relation\` 同 PK 覆盖写:recommendation_level / risk_level 照第二拍出参、recommendation_reason(工具 reason 扩写成人话, ③ 逐条 \`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', item: 'S4_review',
/* 中间审核摆成验收工单的样子(机制是可循环的决策卡);由剧本声明,模型抄不抄都一样 */ /* 中间审核摆成验收工单的样子(机制是可循环的决策卡);由剧本声明,模型抄不抄都一样 */
cardVariant: 'review' as const, 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 按意见改台账重发', intent: '差异评估结论审核:决策卡循环,通过或驳回 + AI 按意见改台账重发',
writes: ['substitution.relation', 'substitution.difference_item', 'validation.record', `${D}.verdict`], writes: ['substitution.relation', 'substitution.difference_item', 'validation.record', `${D}.verdict`],
reads: ['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', item: 'T_release',
intent: '拟落账内容已备好,提交 ACT-GATE-03 交 FAE/RI 放行;被打回就改台账再交', 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`], writes: ['substitution.relation', 'substitution.difference_item', 'validation.record', 'demand.request', `${D}.verdict`],
reads: ['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', view: 'landing-confirm',
tools: [], tools: [],
/* 放行触达世界,必须人按一下 —— 引擎不自动收它 */ /* 放行触达世界,必须人按一下 —— 引擎不自动收它 */
guide: `落账确认那一屏摆着拟写 P-11 主记录与拟回写 P-01。**先看轨迹里有没有「验收被打回重做」**: guide: `落账确认那一屏摆着拟写 P-11 主记录与拟回写 P-01。**这一步不发执行工单、不发决策卡**:放行卡由 submit 发
(基座预检后自己发验收卡),发执行工单等于把同一件事问人两遍(2026-08-27 实测:先一张执行单、人答完才 submit 又一张验收单)。
**先看轨迹里有没有「验收被打回重做」**:
有 → 这一拍不是再提交一遍。照意见改**台账本身**(同 PK 覆盖写,不开新行): 有 → 这一拍不是再提交一遍。照意见改**台账本身**(同 PK 覆盖写,不开新行):
· 他说推荐等级与明细不符 → 核对 \`substitution.relation.recommendation_level\` 与该关系明细的四态计数,不符的把 recommendation_reason 改实、把漏挂的验证要求补上; · 他说推荐等级与明细不符 → 核对 \`substitution.relation.recommendation_level\` 与该关系明细的四态计数,不符的把 recommendation_reason 改实、把漏挂的验证要求补上;
· 他说 P-01 回写不全 → 补 \`demand.request\` 那五项; · 他说 P-01 回写不全 → 补 \`demand.request\` 那五项;
@@ -24,6 +24,7 @@ import { activityIdSchema } from '@contracts';
import { ASSESSMENT_SKILLS } from './skills.ts'; import { ASSESSMENT_SKILLS } from './skills.ts';
import { CMMN_XML, NARRATIVE, ASSESSMENT_PHASES, ASSESSMENT_STEPS } from './case-model.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 { 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'; import { ACTIVITY_ID_TEXT, D, TOOL_COMPARE, TOOL_RECOMMEND } from './constants.ts';
export { D, RULE_VERSION, 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: { identity: {
activityId: ACTIVITY_ID, activityId: ACTIVITY_ID,
name: 'ACT-WORK-05 · 差异评估', name: 'ACT-WORK-05 · 差异评估',
packageVersion: '1.4.7', packageVersion: '1.6.2',
category: '需求分析与方案匹配', category: '需求分析与方案匹配',
sdkVersion: '0.1.0-m5', sdkVersion: '0.1.0-m5',
checksum: 'dev-checksum-part-substitution-assessment', checksum: 'dev-checksum-part-substitution-assessment',
@@ -109,20 +110,57 @@ export const manifest: ActivityManifest = {
data: { data: {
type: 'object', type: 'object',
required: ['customerCode', 'requestDescription', 'originalInternalPn', 'candidateInternalPns'], 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: { properties: {
customerCode: { type: 'string', title: '客户编码(L-02 客户主台账)', examples: ['CU-0142'] }, customerCode: {
customerName: { type: 'string', title: '客户名称(仅客户不在库时填,启动后先建档)', examples: ['深圳恒星电源有限公司'] }, 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: { requestDescription: {
type: 'string', type: 'string',
title: '客户需求(原始描述 —— 据此新建 P-01 需求主记录 · 版本 V1)', title: '客户需求',
description: '保留原始需求语义,据此新建 P-01 需求主记录(版本 V1)。',
examples: ['工业电源项目在用 LM2596S-5.0(TI,12V→5V),原厂 PCN 转 NRND,客户要求验证替代料'], 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: { candidateInternalPns: {
type: 'array', type: 'array',
title: '候选内部料号(数组,≥1;不在库的启动后经数据工单创建)', title: '候选内部料号',
description: '被评估物料;库里有就直接选,没有就自己填料号,启动后经数据工单创建。',
items: { type: 'string' }, items: { type: 'string' },
examples: [['XL2596S-5.0', 'LM2596S-ADJ']], 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: { datasheets: {
type: 'array', type: 'array',
@@ -257,7 +295,7 @@ export const manifest: ActivityManifest = {
skills: ASSESSMENT_SKILLS.map((s) => s.id), skills: ASSESSMENT_SKILLS.map((s) => s.id),
models: ['deepseek-v4-flash'], models: ['deepseek-v4-flash'],
roles: ['FAE/RI'], 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: 'eval-input-list', html: EVAL_INPUT_LIST },
{ name: 'difference-review', html: DIFFERENCE_REVIEW }, { name: 'difference-review', html: DIFFERENCE_REVIEW },
{ name: 'landing-confirm', html: LANDING_CONFIRM }, { name: 'landing-confirm', html: LANDING_CONFIRM },
/* 运营中心 L2 的沙箱视图(2026-08-27,用户定替换掉全流程分支图):关键台账数据看板 —— 最近一次已放行
实例的落账快照,行由基座按 `data` 声明从正式表圈;无脚本,报高脚本由服务端出口注入 */
{ name: 'activity-ledger', html: LEDGER_BOARD, data: LEDGER_BOARD_DATA },
], ],
cardSchemas: {}, cardSchemas: {},
distillHooks: [{ on: 'terminal', prompt: '归纳本次差异评估:哪些候选、各自推荐等级与关键依据、UNKNOWN 挂了什么验证任务、审核是否被驳回过(为什么)、数据工单建了哪些主数据。' }], distillHooks: [{ on: 'terminal', prompt: '归纳本次差异评估:哪些候选、各自推荐等级与关键依据、UNKNOWN 挂了什么验证任务、审核是否被驳回过(为什么)、数据工单建了哪些主数据。' }],
@@ -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');
@@ -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`,不是整表;`<as>.<列>` 是那一集合首行的标量,`<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-<token> 决定;没投出 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 => `<thead><tr>${cols.map((c) => `<th>${c}</th>`).join('')}</tr></thead>`;
export const LEDGER_BOARD = `<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ACT-WORK-05 差异评估 · 关键台账数据 · SS02 需求分析与方案匹配</title>
<style>${TARGET_CSS}${BIND_CSS}</style>
</head>
<body>
<div class="page" data-class-of="board:state">
<header class="mast">
<div class="kick">SS02 需求分析与方案匹配 · 活动台账视图</div>
<h1><span class="code">ACT-WORK-05</span>关键台账数据</h1>
<p class="sub case">视角:本活动最近一次已放行实例 <b data-slot="case:instanceNo"></b>(<b data-slot="case:openedAt"></b> 开案 · <b data-slot="case:releasedAt"></b> 放行 · 负责人 <b data-slot="case:owner" data-blank></b>)的落账快照 · 案号 <b class="mono" data-slot="case:ref"></b> · 关联需求 <b data-slot="demand.request.request_id"></b>(客户 <b data-slot="demand.request.customer_code"></b> <span data-slot="customer.profile.name" data-blank></span>)· 评估基准 <b data-slot="${D}.verdict.original_internal_pn"></b>(<span data-slot="parts.profile.brand_name" data-blank></span>)→ 候选 <span class="cands" data-rows="substitution.relation"><b data-slot="candidate_internal_pn"></b></span> · 规则版本 <b data-slot="${D}.verdict.rule_version"></b></p>
<p class="sub verdict">评估结论:<span data-slot="${D}.verdict.summary" data-blank></span></p>
<div class="legend">
<span class="li"><span class="dot full"></span>正式台账直读 —— 放行落地后的事实,按案号圈出这一单的行</span>
<span class="li"><span class="dot half"></span>同料号的历史验证记录(别单 / 线下留下的,作本次评估的证据引用)</span>
</div>
</header>
<div class="notice none">本活动还没有已放行的实例 —— 有一单放行后,这里摆它的关键台账落账快照。</div>
<div class="notice missing">实例 <b data-slot="case:instanceNo"></b> 已放行,但案号 <b class="mono" data-slot="case:ref"></b> 在正式表里查无 —— 多半是起单时关了「落真库」(主数据只留在活动缓存),这一单没有可摆的落账数据。</div>
<!-- 重要性分层 -->
<div class="layers">
<div class="layer"><b><span class="lg">P-08</span> 芯片主数据</b>核心输入 · 评估原材料,查无即硬阻塞</div>
<div class="layer core-out"><b><span class="lg">P-11</span> 替代批准关系库</b>核心产出 · 结论与明细的落账地</div>
<div class="layer evid"><b><span class="lg">P-10</span> 原厂技术资料库</b>证据链 · 每项结论的版本化出处</div>
<div class="layer loop"><b><span class="lg">P-06</span> 样品验证记录</b>闭环去向 · UNKNOWN 的实测收口</div>
<div class="layer ctx"><b><span class="lg">P-01</span> 客户需求主台账</b>上下文 · 需求源头与回写对象</div>
</div>
<!-- P-01 -->
<section id="p01">
<div class="sec-head">
<h2><span class="lid">P-01</span>客户需求主台账</h2>
<span class="ltag ctx">上下文 · R+W</span>
<span class="why">启动时新建需求;结束时回写风险 / 验证要求 / 输出链接 / 下一步 / 当前阶段</span>
<span class="cnt">正式表 · <i data-slot="demand_request_rows"></i> 条</span>
</div>
<div class="kv">
<div class="cell"><div class="k">需求编号</div><div class="v mono" data-slot="demand.request.request_id"></div></div>
<div class="cell"><div class="k">客户</div><div class="v mono"><span data-slot="demand.request.customer_code"></span> <span class="sub" data-slot="customer.profile.name" data-blank></span></div></div>
<div class="cell"><div class="k">需求版本</div><div class="v mono" data-slot="demand.request.requirement_version"></div></div>
<div class="cell"><div class="k">需求状态</div><div class="v"><span class="st rq" data-class-of="demand.request.request_status"></span></div></div>
<div class="cell span2"><div class="k">需求描述</div><div class="v" data-slot="demand.request.request_description"></div></div>
<div class="cell"><div class="k">应用场景</div><div class="v" data-slot="demand.request.application_scenario" data-blank></div></div>
<div class="cell"><div class="k">BOM 位</div><div class="v mono" data-slot="demand.request.bom_position" data-blank></div></div>
<div class="cell"><div class="k">原内部料号</div><div class="v mono" data-slot="demand.request.original_internal_pn"></div></div>
<div class="cell"><div class="k">候选内部料号</div><div class="v mono cands" data-rows="substitution.relation"><b data-slot="candidate_internal_pn"></b></div></div>
<div class="cell"><div class="k">风险等级(回写)</div><div class="v"><span class="st rk" data-class-of="demand.request.risk_level"></span></div></div>
<div class="cell"><div class="k">验证要求(回写)</div><div class="v mono" data-slot="demand.request.verification_requirement" data-blank></div></div>
<div class="cell"><div class="k">当前阶段</div><div class="v"><span class="st stg" data-class-of="demand.request.current_stage"></span></div></div>
<div class="cell"><div class="k">标准输出链接</div><div class="v mono" data-slot="demand.request.output_ref" data-blank></div></div>
<div class="cell span2"><div class="k">下一步动作</div><div class="v" data-slot="demand.request.next_action" data-blank></div></div>
</div>
</section>
<!-- P-08 -->
<section id="p08">
<div class="sec-head">
<h2><span class="lid">P-08</span>芯片主数据台账</h2>
<span class="ltag in">核心输入 · R(查无 → 数据工单创建)</span>
<span class="why">原 PN 与候选 PN 的物料基准 + 标准化参数集(子表 P-08 参数),逐项比对的原材料</span>
<span class="cnt">正式表 · <i data-slot="parts_profile_rows"></i> 条(本活动相关)</span>
</div>
<div class="tbl-wrap">
<table>
${th('内部料号', '品牌', '规格描述', '封装', '生命周期', 'Datasheet 版本', '标准化参数集', '适配场景')}
<tbody data-rows="parts.profile">
<tr>
<td class="pn mono"><span data-slot="internal_pn"></span><span class="pnrow" data-rows="${D}.pn_check" data-by="internal_pn"><i class="st rl" data-class-of="role"></i><i class="st ex" data-class-of="exists_status"></i></span></td>
<td data-slot="brand_name" data-blank></td>
<td data-slot="specification_description" data-blank></td>
<td class="mono" data-slot="package" data-blank></td>
<td><span class="st lc" data-class-of="lifecycle_status"></span></td>
<td class="mono" data-slot="datasheet_version" data-blank></td>
<td><div class="pchips" data-rows="parts.parameter" data-by="internal_pn"><span><i data-slot="param_name"></i><b data-slot="param_value" data-blank></b><em data-slot="param_unit" data-blank></em></span></div></td>
<td data-slot="application_scenarios" data-blank></td>
</tr>
</tbody>
</table>
</div>
<p class="aband">本轮放弃的候选(未建档、未评估):<span data-rows="${D}.pn_check"><i data-class-of="exists_status" data-slot="internal_pn"></i></span></p>
</section>
<!-- P-11 -->
<section id="p11">
<div class="sec-head">
<h2><span class="lid">P-11</span>替代选型与批准关系库 · 替代关系主记录</h2>
<span class="ltag out">核心产出 · R+W</span>
<span class="why">每个候选一条关系记录;客户批准状态为只读边界,本活动不写</span>
<span class="cnt">正式表 · <i data-slot="substitution_relation_rows"></i> 条</span>
</div>
<div class="tbl-wrap" style="margin-bottom:14px">
<table>
${th('替代关系 ID', '原 → 候选', '替代类型', '推荐等级', '分析状态', '风险', '差异摘要', '验证要求 / 回链', '规则版本', '客户批准(只读)')}
<tbody data-rows="substitution.relation">
<tr>
<td class="mono" data-slot="substitution_relation_id"></td>
<td class="mono"><span data-slot="original_internal_pn"></span> → <b data-slot="candidate_internal_pn"></b></td>
<td><span class="enumtxt ty" data-class-of="substitution_type"></span></td>
<td><span class="st rec" data-class-of="recommendation_level"></span></td>
<td><span class="st an" data-class-of="analysis_status"></span></td>
<td><span class="st rk" data-class-of="risk_level"></span></td>
<td><span data-slot="difference_summary" data-blank></span><span class="sub" data-slot="recommendation_reason" data-blank></span></td>
<td class="mono"><span data-slot="validation_id" data-blank></span><span class="sub" data-slot="verification_requirement" data-blank></span></td>
<td class="mono" data-slot="rule_version" data-blank></td>
<td><span class="st ap" data-class-of="customer_approval_status"></span></td>
</tr>
</tbody>
</table>
</div>
<div class="sec-head" style="margin-bottom:10px">
<h2 style="font-size:13.5px"><span class="lid">P-11</span>差异明细子表(由双方 P-08 标准化参数集逐项比对得出;MATCH 项淡显)</h2>
<span class="cnt"><i data-slot="substitution_difference_item_rows"></i> 条 · MATCH <i data-slot="substitution_difference_item_difference_status_match"></i> · MISMATCH <i data-slot="substitution_difference_item_difference_status_mismatch"></i> · UNKNOWN <i data-slot="substitution_difference_item_difference_status_unknown"></i> · CONDITIONAL <i data-slot="substitution_difference_item_difference_status_conditional"></i></span>
</div>
<div class="tbl-wrap">
<table>
${th('所属关系', '参数项', '原值 → 候选值', '约束', '结论', '风险', '证据引用', '影响说明')}
<tbody data-rows="substitution.difference_item">
<tr data-class-of="difference_status">
<td class="mono" data-slot="substitution_relation_id"></td>
<td data-slot="parameter_name"></td>
<td class="mono"><span data-slot="original_value" data-blank></span> → <span data-slot="candidate_value" data-blank></span> <span data-slot="parameter_unit" data-blank></span></td>
<td><span class="enumtxt con" data-class-of="constraint_type"></span></td>
<td><span class="st dst" data-class-of="difference_status"></span></td>
<td><span class="st rk" data-class-of="risk_level"></span></td>
<td class="mono" data-slot="evidence_ref" data-blank></td>
<td><span data-slot="impact_statement" data-blank></span><span class="sub" data-slot="unknown_disposition" data-blank></span></td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- P-10 -->
<section id="p10">
<div class="sec-head">
<h2><span class="lid">P-10</span>原厂技术资料库</h2>
<span class="ltag evid">证据链 · R(查无 → 数据工单创建)</span>
<span class="why">每项结论的 datasheet 出处,版本回链</span>
<span class="cnt">正式表 · <i data-slot="parts_document_rows"></i> 条(本活动相关料号)</span>
</div>
<div class="tbl-wrap">
<table>
${th('关联料号', '品牌', '资料类型', '资料文件', '版本', '状态')}
<tbody data-rows="parts.document">
<tr>
<td class="mono"><span data-slot="internal_pn"></span> · <span data-slot="doc_seq" data-blank></span></td>
<td data-slot="brand_name" data-blank></td>
<td><span class="enumtxt dt" data-class-of="document_type"></span></td>
<td class="mono" data-slot="document_ref" data-blank></td>
<td class="mono" data-slot="document_version" data-blank></td>
<td><span class="st ds" data-class-of="document_status"></span></td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- P-06 -->
<section id="p06">
<div class="sec-head">
<h2><span class="lid">P-06</span>样品交付与芯片验证记录台账</h2>
<span class="ltag loop">闭环去向 · R + 条件写(不可创建,只能实测产生)</span>
<span class="why">UNKNOWN / CONDITIONAL 的收口处;验证完成后回链 P-11</span>
<span class="cnt">本单挂的 · <i data-slot="validation_record_rows"></i> 条 · 同料号历史 · <i data-slot="validation_history_rows"></i> 条</span>
</div>
<div class="tbl-wrap">
<table>
${th('验证 ID', '关联需求', '验证料号', '验证类型', '测试条件', '结果 / 状态', '问题与风险', '后续动作')}
<tbody data-rows="validation.record">
<tr>
<td class="mono" data-slot="validation_id"></td>
<td class="mono" data-slot="request_id" data-blank></td>
<td class="mono" data-slot="validation_internal_pn"></td>
<td><span class="enumtxt vt" data-class-of="validation_type"></span></td>
<td data-slot="test_condition" data-blank></td>
<td><span class="st vs" data-class-of="validation_status"></span><span class="sub" data-slot="test_result" data-blank></span><span class="sub mono" data-slot="evidence_ref" data-blank></span></td>
<td data-slot="issue_risk" data-blank></td>
<td data-slot="next_validation_action" data-blank></td>
</tr>
</tbody>
<tbody class="hist" data-rows="validation.history">
<tr>
<td class="mono" data-slot="validation_id"></td>
<td class="mono" data-slot="request_id" data-blank></td>
<td class="mono" data-slot="validation_internal_pn"></td>
<td><span class="enumtxt vt" data-class-of="validation_type"></span></td>
<td data-slot="test_condition" data-blank></td>
<td><span class="st vs" data-class-of="validation_status"></span><span class="sub" data-slot="test_result" data-blank></span><span class="sub mono" data-slot="evidence_ref" data-blank></span></td>
<td data-slot="issue_risk" data-blank></td>
<td data-slot="next_validation_action" data-blank></td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- 次要台账 -->
<section id="minor">
<div class="sec-head">
<h2>次要台账(只读引用,不进主看板)</h2>
</div>
<div class="minor">
<div class="m"><b><span class="lid">P-09</span>客户技术与机会档案</b>提供 BOM 位与客户技术栈背景;缺口由执行工单补齐。</div>
<div class="m"><b><span class="lid">EXT-02</span>外部来源清单</b>登记本次评估用到的可信外部来源(原厂官网 datasheet 等)。</div>
<div class="m"><b><span class="lid">GOV</span>评估规则库</b>四态判定口径;本次锁定版本 <span class="mono" data-slot="${D}.verdict.rule_version"></span>,随结论一并落账。</div>
</div>
</section>
<footer>
<span>SS02 · ACT-WORK-05 差异评估 · 关键台账数据视图</span>
<span style="font-family:var(--mono)">数据来源:正式台账(parts / demand / substitution / validation / sub_eval)· 实例 <span data-slot="case:instanceNo"></span> · 案号 <span data-slot="case:ref"></span></span>
</footer>
</div>
</body>
</html>
`;
@@ -140,6 +140,14 @@ ${badgeCss('.ct', [
/* ==================== 执行工单(评估输入清单):徽标别名 ==================== */ /* ==================== 执行工单(评估输入清单):徽标别名 ==================== */
const BIND_EXEC = ` 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 徽标基座 —— /* 存在性核对的两列(角色 / 在库状态)与评估输入清单的状态列共用目标件自己的 .st 徽标基座 ——
取值域互不重叠(found/absent/created/abandoned 与 gap/default/ok 没有一个词撞车)。 取值域互不重叠(found/absent/created/abandoned 与 gap/default/ok 没有一个词撞车)。
四档配色**原样照抄目标件自己的 .st.bound / .st.ok / .st.miss / .st.def 那四条声明**,只换成 v- 版。 */ 四档配色**原样照抄目标件自己的 .st.bound / .st.ok / .st.miss / .st.def 那四条声明**,只换成 v- 版。 */
@@ -181,14 +189,21 @@ ${badgeCss('.st.dst', [
['match', 'MATCH', OK], ['match', 'MATCH', OK],
['mismatch', 'MISMATCH', DANGER], ['mismatch', 'MISMATCH', DANGER],
['unknown', 'UNKNOWN', AMBER], ['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], ['recommended', '推荐', OK],
['conditional', '条件推荐', 'color: var(--ledger); background: var(--ledger-soft); border-color: var(--ledger-line);'], ['conditional', '有条件推荐', 'color: var(--amber); background: var(--amber-soft); border-color: var(--amber-line);'],
['alternate', '备选', AMBER], ['alternate', '备选', 'color: var(--amber); background: var(--amber-soft); border-color: var(--amber-line);'],
['not_recommended', '不推荐', DANGER], ['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 一档:这一屏「各候选 * 风险等级(原样照抄目标件 .risk.low/.mid/.hi 三条声明)——加 critical 一档:这一屏「各候选
* 推荐等级」摘要读的是 substitution.relation.risk_level(声明了 low/medium/high/critical * 推荐等级」摘要读的是 substitution.relation.risk_level(声明了 low/medium/high/critical
@@ -196,7 +211,7 @@ ${badgeCss('.st.rec', [
*/ */
${badgeCss('.risk', [ ${badgeCss('.risk', [
['low', '低', 'color: var(--ok);'], ['low', '低', 'color: var(--ok);'],
['medium', '中', 'color: #8f6a12;'], ['medium', '中', 'color: var(--amber, #8f6a12);'],
['high', '高', 'color: var(--danger);'], ['high', '高', 'color: var(--danger);'],
['critical', '危急', 'color: var(--danger); font-weight: 900;'], ['critical', '危急', 'color: var(--danger); font-weight: 900;'],
])} ])}
@@ -246,7 +261,7 @@ ${badgeCss('.st', [
/* 风险等级比验收 01 多一档 critical(P-11 主记录的 risk_level 声明了它,差异明细没有) */ /* 风险等级比验收 01 多一档 critical(P-11 主记录的 risk_level 声明了它,差异明细没有) */
${badgeCss('.risk', [ ${badgeCss('.risk', [
['low', '低', 'color: var(--ok);'], ['low', '低', 'color: var(--ok);'],
['medium', '中', 'color: #8f6a12;'], ['medium', '中', 'color: var(--amber, #8f6a12);'],
['high', '高', 'color: var(--danger);'], ['high', '高', 'color: var(--danger);'],
['critical', '危急', 'color: var(--danger); font-weight: 900;'], ['critical', '危急', 'color: var(--danger); font-weight: 900;'],
])} ])}
@@ -496,6 +511,25 @@ const PN_CHECK_TABLE = `<div class="jc-tbl-wrap"><table>
</tr></tbody> </tr></tbody>
</table></div>`; </table></div>`;
/** 候选参数现值 —— **只读参考,基座从台账现读**(2026-08-27,用户三次指出「库里明明有值,清单还要填一遍」):
* 从前「待确认」行由模型判「哪个参数原厂未披露」再写进清单 —— 参数值有没有是台账里的事实,不该由模型宣布;
* 现在每颗料一张小表(外层按核对表逐颗复制,里层 `data-by="internal_pn"` 只列它名下的参数行),
* 人看得见现值;未披露的由差异评估判成 UNKNOWN 并挂验证任务。 */
const PARAM_REF = `<article class="prefs" data-rows="mine:${D}.pn_check"><section class="pref">
<div class="pref-head"><b class="mono" data-slot="internal_pn" data-blank></b><span class="rl" data-class-of="role"></span>${badge('st', 'exists_status')}</div>
<div class="jc-tbl-wrap"><table>
<thead><tr><th style="width:38px">#</th><th data-h="参数名">参数名</th><th data-h="值">值</th><th data-h="单位">单位</th><th data-h="约束类型">约束类型</th><th data-h="说明">说明</th></tr></thead>
<tbody data-rows="parts.parameter" data-by="internal_pn"><tr>
<td class="rn"><span class="jc-rn-num"></span></td>
<td>${ro('param_name')}</td>
<td>${ro('param_value', 'mono')}</td>
<td>${ro('param_unit')}</td>
<td><span class="jc-vcell ro"><span class="ct" data-class-of="constraint_type"></span></span></td>
<td>${ro('param_note')}</td>
</tr></tbody>
</table></div>
</section></article>`;
const GAP_TABLE = `<div class="jc-tbl-wrap"><table id="tbl"> const GAP_TABLE = `<div class="jc-tbl-wrap"><table id="tbl">
<thead><tr> <thead><tr>
<th style="width:38px">#</th> <th style="width:38px">#</th>
@@ -530,13 +564,16 @@ export const EVAL_INPUT_LIST = doc({
zone('做什么', '启动 4 项已绑定,其余信息在工单里补齐') + zone('做什么', '启动 4 项已绑定,其余信息在工单里补齐') +
note([ note([
'<span class="done">AI 已完成</span>:系统已按启动传入的 <b>4 项</b>完成绑定——客户、客户需求(新建 P-01 · 版本 V1)、原 PN、候选 PN(数组,逐个评估)——并生成输入快照。', '<span class="done">AI 已完成</span>:系统已按启动传入的 <b>4 项</b>完成绑定——客户、客户需求(新建 P-01 · 版本 V1)、原 PN、候选 PN(数组,逐个评估)——并生成输入快照。',
'<span class="need">需要你补齐</span>:启动时未收的信息(应用场景、BOM 位)<b>直接在下表补</b>;默认值(替代类型 / 规则版本 / 负责人期限)可确认可改。本单缺口只覆盖「<b>有而不全</b>」;「完全没有」(料号不在库)已由数据工单创建。未补齐的缺口项将按 <b>UNKNOWN</b> 进入差异评估。', '<span class="need">需要你补齐</span>:启动时未收的信息(应用场景、BOM 位)<b>直接在下表补</b>;默认值(替代类型 / 规则版本 / 负责人期限)可确认可改。「完全没有」(料号不在库)已由数据工单创建;每颗料的参数现值摆在「候选参数现值」里,写着「未披露」的参数将按 <b>UNKNOWN</b> 进入差异评估并挂验证任务,不用在这里补。',
]) + ]) +
zone('料号存在性核对', '只读参考 · 与下表「补充内容」互不冲突') + zone('料号存在性核对', '只读参考 · 与下表「补充内容」互不冲突') +
PN_CHECK_TABLE + PN_CHECK_TABLE +
zone('候选参数现值', '只读参考 · P-08 标准化参数集,一颗料一张,基座从台账现读;写着「未披露」的由差异评估判成 UNKNOWN 并挂验证任务') +
PARAM_REF +
zone('交回什么', '评估输入清单(已绑定项照实核对,缺口项补齐,*为必填)') + zone('交回什么', '评估输入清单(已绑定项照实核对,缺口项补齐,*为必填)') +
gapbar(`${D}_input_gap`, '补充内容') + // 红带说「必填格」不说「补充内容」:缺的可能是 AI 该写的输入项(2026-08-27 实测),写死列名会把人指向错的格子
editGate(`${D}.input_gap`, `<b>还有 <span data-slot="${D}_input_gap_missing"></span> 项待补 / 待确认</b>,点「编辑」在「补充内容」列填入 —— 已绑定那几行的「输入项/来源/绑定值」抄的是启动入参,发现记录本身有误请退回触发方重新启动,不要在这里改`) + gapbar(`${D}_input_gap`, '必填格') +
editGate(`${D}.input_gap`, `<b>还有 <span data-slot="${D}_input_gap_missing"></span> 项待补</b>,点「编辑」在「补充内容」列填入 —— 已绑定那几行的「输入项/来源/绑定值」抄的是启动入参,发现记录本身有误请退回触发方重新启动,不要在这里改`) +
GAP_TABLE + GAP_TABLE +
addrow(`${D}.input_gap`, '+ 加一行(补充资料项)', '新行的「键」必须写(它是这一行的钥匙,写一个英文短名,如 thermal_note);「输入项」与「补充内容」也是必填;删一行 = 该输入项不适用;补不了的写「留 UNKNOWN」或「挂验证任务」') + addrow(`${D}.input_gap`, '+ 加一行(补充资料项)', '新行的「键」必须写(它是这一行的钥匙,写一个英文短名,如 thermal_note);「输入项」与「补充内容」也是必填;删一行 = 该输入项不适用;补不了的写「留 UNKNOWN」或「挂验证任务」') +
flow( flow(
@@ -546,42 +583,59 @@ export const EVAL_INPUT_LIST = doc({
/* ==================== 局面 2 · 差异评估结论审核(搬自「验收工单-01」)==================== */ /* ==================== 局面 2 · 差异评估结论审核(搬自「验收工单-01」)==================== */
const RELATION_SUMMARY_TABLE = `<div class="tbl-wrap"><table> /** 候选 PN 结论汇总 —— 目标件 2026-08-27 改版:一个候选一条结论(P-11 主表草稿)。
* 品牌不在 relation 上:用嵌套行模版 `data-by` 从 `parts.profile` 整表里挑这一颗料那一行(取值,不是算)。
* 「差异统计(四态)」绑 difference_summary —— S3 剧本让模型按「7 项比对:4 MATCH / 1 MISMATCH / …」的形写它,
* 模版不数四态(数出来的画面一律不做假)。 */
const RELATION_SUMMARY_TABLE = `<div class="tbl-wrap" style="margin-bottom:20px"><table>
<thead><tr> <thead><tr>
<th style="width:40px">#</th> <th style="width:40px">#</th>
<th data-h="候选料号">候选料号</th> <th data-h="候选PN">候选 PN</th>
<th data-h="替代类型">替代类型</th> <th data-h="品牌">品牌</th>
<th data-h="推荐等级">推荐等级</th> <th data-h="推荐程度">推荐程度</th>
<th data-h="风险等级">风险等级</th> <th data-h="风险级别">风险级别</th>
<th data-h="推荐理由">推荐理由</th> <th data-h="差异统计">差异统计(四态)</th>
<th data-h="差异摘要">差异摘要</th> <th data-h="结论摘要">结论摘要</th>
</tr></thead> </tr></thead>
<tbody data-rows="mine:substitution.relation"><tr> <tbody data-rows="mine:substitution.relation"><tr>
<td class="num"></td> <td class="num"></td>
<td class="mono" data-slot="candidate_internal_pn" data-blank></td> <td class="cust mono" data-slot="candidate_internal_pn" data-blank></td>
<td><span class="enumtxt" data-class-of="substitution_type"></span></td> <td><span data-rows="parts.profile" data-by="internal_pn=candidate_internal_pn"><i data-slot="brand_name" data-blank></i></span></td>
<td>${badge('st rec', 'recommendation_level')}</td> <td>${badge('rec', 'recommendation_level')}</td>
<td>${badge('risk', 'risk_level')}</td> <td>${badge('risk', 'risk_level')}</td>
<td class="stat" data-slot="difference_summary" data-blank></td>
<td class="reason" data-slot="recommendation_reason" data-blank></td> <td class="reason" data-slot="recommendation_reason" data-blank></td>
<td class="reason" data-slot="difference_summary" data-blank></td>
</tr></tbody> </tr></tbody>
</table></div>`; </table></div>`;
const DIFFERENCE_ITEM_TABLE = `<div class="tbl-wrap"><table> /** 差异明细 · 按候选 PN 分组 —— 目标件 2026-08-27 改版:**一个候选一张表**(用户:「存在多个替代 PN 要对比差异时看不清楚」)。
<thead><tr><th style="width:40px">#</th><th data-h="参数项">参数项</th><th data-h="原PN值">原 PN 值</th><th data-h="候选PN值">候选 PN 值</th><th data-h="单位">单位</th><th data-h="约束类型">约束类型</th><th data-h="结论">结论</th><th data-h="风险等级">风险等级</th><th data-h="证据引用">证据引用</th><th data-h="AI置信度">AI 置信度</th></tr></thead> * 外层容器 <article> 按 relation 逐个复制行模版 <section class="cand">,里层 <tbody data-by="substitution_relation_id">
<tbody data-rows="mine:substitution.difference_item"><tr> * 只列该候选名下的明细(渲染器 2026-08-27 补的嵌套 + 父键过滤)。容器标签名不得在行模版内部再出现:
<td class="num"></td> * <article> 里是 section / div / table,不撞。 */
<td class="cust" data-slot="parameter_name" data-blank></td> const CANDIDATE_SECTIONS = `<article class="cands" data-rows="mine:substitution.relation"><section class="cand">
<td class="mono" data-slot="original_value" data-blank></td> <div class="cand-head">
<td class="mono" data-slot="candidate_value" data-blank></td> <span class="cand-idx"></span>
<td data-slot="parameter_unit" data-blank></td> <span class="cand-name mono" data-slot="candidate_internal_pn" data-blank></span>
<td><span class="con" data-class-of="constraint_type"></span></td> <span class="cand-brand" data-rows="parts.profile" data-by="internal_pn=candidate_internal_pn"><i data-slot="brand_name" data-blank></i></span>
<td>${badge('st dst', 'difference_status')}</td> <span class="cand-vs">vs 原 PN · <i data-slot="original_internal_pn" data-blank></i></span>
<td>${badge('risk', 'risk_level')}</td> <span class="cand-con">${badge('rec', 'recommendation_level')}${badge('risk', 'risk_level')}</span>
<td class="mono" data-slot="evidence_ref" data-blank></td> </div>
<td class="mono" data-slot="confidence" data-blank></td> <div class="tbl-wrap"><table>
</tr></tbody> <thead><tr><th style="width:40px">#</th><th data-h="参数项">参数项</th><th data-h="原PN值">原 PN 值</th><th data-h="候选PN值">候选 PN 值</th><th data-h="单位">单位</th><th data-h="约束类型">约束类型</th><th data-h="结论">结论</th><th data-h="风险等级">风险等级</th><th data-h="证据引用">证据引用</th><th data-h="AI置信度">AI 置信度</th></tr></thead>
</table></div>`; <tbody data-rows="mine:substitution.difference_item" data-by="substitution_relation_id"><tr>
<td class="num"></td>
<td class="cust" data-slot="parameter_name" data-blank></td>
<td class="mono" data-slot="original_value" data-blank></td>
<td class="mono" data-slot="candidate_value" data-blank></td>
<td data-slot="parameter_unit" data-blank></td>
<td><span class="con" data-class-of="constraint_type"></span></td>
<td>${badge('st dst', 'difference_status')}</td>
<td>${badge('risk', 'risk_level')}</td>
<td class="mono" data-slot="evidence_ref" data-blank></td>
<td class="mono" data-slot="confidence" data-blank></td>
</tr></tbody>
</table></div>
</section></article>`;
export const DIFFERENCE_REVIEW = doc({ export const DIFFERENCE_REVIEW = doc({
css: ACC1_CSS, css: ACC1_CSS,
@@ -595,18 +649,18 @@ export const DIFFERENCE_REVIEW = doc({
body: body:
zone('AI 结论说明', '结论是怎么算出来的') + zone('AI 结论说明', '结论是怎么算出来的') +
note([ note([
'<b>AI 已完成的计算</b>:按 GOV 评估规则(锁定版本 <b class="mono" data-slot="rule_version" data-blank></b>),对原 PN <b class="mono" data-slot="original_internal_pn" data-blank></b> 与每个候选逐项比对,产出四态结论——<b>MATCH / MISMATCH / UNKNOWN / CONDITIONAL</b>,并给出风险等级、证据引用与置信度。差异明细 = <b>P-11 差异明细子表</b>(一条参数差异一条记录);当前为 AI 草稿,<b>FAE 审核通过前不落账</b>。评估参数可能含本次数据工单创建的主数据(确认人背书;未附 datasheet 的参数项会显式标注「人工录入」)。', '<b>AI 已完成的计算</b>:按 GOV 评估规则(锁定版本 <b class="mono" data-slot="rule_version" data-blank></b>),以原 PN <b class="mono" data-slot="original_internal_pn" data-blank></b> 为基准,对 <b data-slot="substitution_relation_rows"></b> 个候选 PN 分别逐项比对,每项产出四态结论——<b>MATCH / MISMATCH / UNKNOWN / CONDITIONAL</b>,并给出风险等级、证据引用与置信度;再按候选汇总出下方的【候选 PN 结论汇总】(一个候选一条结论)。差异明细 = <b>P-11 差异明细子表</b>(一条参数差异一条记录,按候选分组);结论汇总 = <b>P-11 主表草稿</b>。当前均为 AI 草稿,<b>FAE 审核通过前不落账</b>。评估参数可能含本次数据工单创建的主数据(确认人背书;未附 datasheet 的参数项会显式标注「人工录入」)。',
]) + ]) +
criteria('判断这份差异评估结论是否可信,请逐项核对', [ criteria('判断这份差异评估结论是否可信,请逐项核对', [
'<b>关键参数差异均有明确状态</b>:逐项结论无漏项,每个参数都有四态之一的判定;', '<b>汇总结论与明细一致</b>:结论表中每个候选的推荐程度 / 风险级别,与其下方差异明细表的四态分布吻合(存在 MISMATCH 硬约束或悬空 UNKNOWN 的候选不得给「推荐」);',
'<b>关键参数差异均有明确状态</b>:每个候选的逐项结论无漏项,每个参数都有四态之一的判定;',
'<b>UNKNOWN 项均有去向</b>:每个 UNKNOWN 都挂了验证任务或补来源动作,不允许悬空;', '<b>UNKNOWN 项均有去向</b>:每个 UNKNOWN 都挂了验证任务或补来源动作,不允许悬空;',
'<b>证据链完整</b>:每项结论可追溯至 P-08 / P-10 / P-06 的具体记录,或本单数据工单的创建记录(标注:人工录入);', '<b>证据链完整</b>:每项结论可追溯至 P-08 / P-10 / P-06 的具体记录,或本单数据工单的创建记录(标注:人工录入);硬约束项未经证据支持不得判 MATCH。',
'<b>硬约束项未被自动判 MATCH</b>:封装 / 工艺等硬约束未经证据支持不得判 MATCH。',
]) + ]) +
zone('各候选推荐等级', '只读展示(P-11 主记录草稿,一个候选一条)') + zone('候选 PN 结论汇总', '每个候选一条结论,内容 = 对应下方明细表的汇总判定(P-11 主表草稿)') +
RELATION_SUMMARY_TABLE + RELATION_SUMMARY_TABLE +
zone('AI 产出的差异明细', '只读展示(P-11 差异明细子表草稿 · 一条参数一条记录 difference_item_id)') + zone('差异明细 · 按候选 PN 分组', '每个候选一张表,与原 PN 逐项对比(P-11 差异明细子表草稿 · 一条参数一条记录 difference_item_id)') +
DIFFERENCE_ITEM_TABLE + CANDIDATE_SECTIONS +
flow( flow(
'<b>审批:</b>本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。审核意见与补充条件写入 P-06(验证要求 verification_requirement);<b>通过后去向:</b>进入步骤 5 · 评审放行与落账确认。', '<b>审批:</b>本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。审核意见与补充条件写入 P-06(验证要求 verification_requirement);<b>通过后去向:</b>进入步骤 5 · 评审放行与落账确认。',
), ),
@@ -208,13 +208,22 @@ tr.ro td{background:var(--soft)}
table.on td>.jc-vcell.ro{display:block} 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 = ` export const ACC1_CSS = `
:root{ :root{
--ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e; --ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e;
--line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb; --line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb;
--brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff; --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; --ok:#278717; --ok-soft:#e8f8e4; --danger:#d83931; --danger-soft:#feeae9;
--amber:#8f6a12; --amber-soft:#fdf3d7; --amber-line:#ecd9a0;
--mono:"SF Mono",Menlo,Consolas,monospace; --mono:"SF Mono",Menlo,Consolas,monospace;
} }
*{box-sizing:border-box} *{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} 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} .page{margin:0;padding:0}
.card{background:#fff;overflow:hidden;border:0;border-radius:0;box-shadow:none} .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%)} .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(178,58,46,.25)} .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{flex:1;min-width:0}
.tit .kick{font-size:11px;letter-spacing:.08em;color:var(--ink-4);font-weight:700} .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} .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)} 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} td{padding:11px 12px;border-bottom:1px solid var(--line-soft);vertical-align:top}
tr:last-child td{border-bottom:0} 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} .num{font-family:var(--mono);color:var(--ink-4);white-space:nowrap}
.cust{font-weight:700} .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)} .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{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)} .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} .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}} @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{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.ok{color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4}
.st.miss{color:var(--danger);background:var(--danger-soft);border-color:#f3b9b5} .st.miss{color:var(--danger);background:#feeae9;border-color:#f3b9b5}
.st.unknown{color:#8f6a12;background:#fdf3d7;border-color:#ecd9a0} .st.unknown{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)}
.st.cond{color:var(--ledger);background:var(--ledger-soft);border-color:var(--ledger-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{font-weight:800;font-size:12.5px}
.risk.low{color:var(--ok)} .risk.low{color:var(--ok)}
.risk.mid{color:#8f6a12} .risk.mid{color:var(--amber)}
.risk.hi{color:var(--danger)} .risk.hi{color:var(--danger)}
.mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)} .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 = ` export const ACC2_CSS = `
@@ -277,7 +302,7 @@ export const ACC2_CSS = `
--ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e; --ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e;
--line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb; --line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb;
--brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff; --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; --ok:#278717; --ok-soft:#e8f8e4; --danger:#d83931; --danger-soft:#feeae9;
--mono:"SF Mono",Menlo,Consolas,monospace; --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} 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} .page{margin:0;padding:0}
.card{background:#fff;overflow:hidden;border:0;border-radius:0;box-shadow:none} .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%)} .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(178,58,46,.25)} .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{flex:1;min-width:0}
.tit .kick{font-size:11px;letter-spacing:.08em;color:var(--ink-4);font-weight:700} .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} .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.ok{color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4}
.st.miss{color:var(--danger);background:var(--danger-soft);border-color:#f3b9b5} .st.miss{color:var(--danger);background:var(--danger-soft);border-color:#f3b9b5}
.st.unknown{color:#8f6a12;background:#fdf3d7;border-color:#ecd9a0} .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{font-weight:800;font-size:12.5px}
.risk.mid{color:#8f6a12} .risk.mid{color:#8f6a12}
.mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)} .mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)}
@@ -235,6 +235,8 @@ P-11 一条(`LM2596S-3.3 → LM2596S-ADJ | alternate | fae_confirmed`);P-01 回
| X | 两颗缺库只问了一次;`expectDecision` 只看张数,审核卡就把它喂饱 | 剧本「还有 absent → 写骨架再发卡」把「先问」压掉;断言太宽 | 剧本补「第二颗也要先问」;脚本新增 `expectAsks`(按问法数) | 活动 + 脚本 | | X | 两颗缺库只问了一次;`expectDecision` 只看张数,审核卡就把它喂饱 | 剧本「还有 absent → 写骨架再发卡」把「先问」压掉;断言太宽 | 剧本补「第二颗也要先问」;脚本新增 `expectAsks`(按问法数) | 活动 + 脚本 |
| Y | 模型漏铺骨架 → 闸如实拦「一行都没有」→ 脚本只会填已有行,连吃 400 抛出 | 脚本没有「加一行」这条路,而人在浏览器里正是这么做的 | 主键齐了就加一行(与件上「加一行」同一条路) | 脚本 | | Y | 模型漏铺骨架 → 闸如实拦「一行都没有」→ 脚本只会填已有行,连吃 400 抛出 | 脚本没有「加一行」这条路,而人在浏览器里正是这么做的 | 主键齐了就加一行(与件上「加一行」同一条路) | 脚本 |
| Z | 铺完骨架就标 `created`、直接 finish,撞「从没给它发过卡」连拒五拍 | 我 1.3.1 的措辞「表里真有一行才标 created」—— 而骨架是模型自己铺的 | 「created 认的是卡交回来了」;顺序写死:骨架 → 发卡 → 拍结束 → 人交回 → 才标 | 活动 | | 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`): **可推广的三条**(已记 `docs/debt.md`):
@@ -9,8 +9,9 @@
--ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e; --ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e;
--line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb; --line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb;
--brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff; --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; --ok:#278717; --ok-soft:#e8f8e4; --danger:#d83931; --danger-soft:#feeae9;
--amber:#8f6a12; --amber-soft:#fdf3d7; --amber-line:#ecd9a0;
--mono:"SF Mono",Menlo,Consolas,monospace; --mono:"SF Mono",Menlo,Consolas,monospace;
} }
*{box-sizing:border-box} *{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} 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} .page{margin:0;padding:0}
.card{background:#fff;overflow:hidden;border:0;border-radius:0;box-shadow:none} .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%)} .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(178,58,46,.25)} .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{flex:1;min-width:0}
.tit .kick{font-size:11px;letter-spacing:.08em;color:var(--ink-4);font-weight:700} .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} .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)} 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} td{padding:11px 12px;border-bottom:1px solid var(--line-soft);vertical-align:top}
tr:last-child td{border-bottom:0} 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} .num{font-family:var(--mono);color:var(--ink-4);white-space:nowrap}
.cust{font-weight:700} .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)} .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{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)} .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} .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}} @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{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.ok{color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4}
.st.miss{color:var(--danger);background:var(--danger-soft);border-color:#f3b9b5} .st.miss{color:var(--danger);background:#feeae9;border-color:#f3b9b5}
.st.unknown{color:#8f6a12;background:#fdf3d7;border-color:#ecd9a0} .st.unknown{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)}
.st.cond{color:var(--ledger);background:var(--ledger-soft);border-color:var(--ledger-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{font-weight:800;font-size:12.5px}
.risk.low{color:var(--ok)} .risk.low{color:var(--ok)}
.risk.mid{color:#8f6a12} .risk.mid{color:var(--amber)}
.risk.hi{color:var(--danger)} .risk.hi{color:var(--danger)}
.mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)} .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}
</style> </style>
</head> </head>
<body> <body>
@@ -90,113 +107,275 @@ tr:last-child td{border-bottom:0}
<p class="zone"><b>【AI 结论说明】</b>—— 结论是怎么算出来的</p> <p class="zone"><b>【AI 结论说明】</b>—— 结论是怎么算出来的</p>
<div class="note"> <div class="note">
<p><b>AI 已完成的计算</b>:按 GOV 评估规则(锁定版本 <b>R-2026.07</b>,rule_version),对 Original=<b>LM2596S-5.0(TI)</b> / Candidate=<b>XL2596S-5.0(XLSEMI)</b> 逐项比对,产出四态结论——<b>MATCH / MISMATCH / UNKNOWN / CONDITIONAL</b>,并给出风险等级、证据引用与置信度。差异明细 = <b>P-11 差异明细子表</b>(一条参数差异一条记录,difference_item_id);当前为 AI 草稿,<b>FAE 审核通过前不落账</b>。评估参数可能含本次数据工单创建的主数据(确认人背书;未附 datasheet 的参数项会显式标注「人工录入」)。</p> <p><b>AI 已完成的计算</b>:按 GOV 评估规则(锁定版本 <b>R-2026.07</b>,rule_version),以 Original=<b>LM2596S-5.0(TI)</b> 为基准,对 <b data-slot="out.candidate_count">2</b> 个候选 PN 分别逐项比对,每项产出四态结论——<b>MATCH / MISMATCH / UNKNOWN / CONDITIONAL</b>,并给出风险等级、证据引用与置信度;再按候选汇总出下方的【候选 PN 结论汇总】(一个候选一条结论)。差异明细 = <b>P-11 差异明细子表</b>(一条参数差异一条记录,difference_item_id,按 candidate_pn 分组);结论汇总 = <b>P-11 主表草稿</b>。当前均为 AI 草稿,<b>FAE 审核通过前不落账</b>。评估参数可能含本次数据工单创建的主数据(确认人背书;未附 datasheet 的参数项会显式标注「人工录入」)。</p>
</div> </div>
<p class="zone"><b>【验收判据】</b>—— 你据此判断「通过 / 驳回」</p> <p class="zone"><b>【验收判据】</b>—— 你据此判断「通过 / 驳回」</p>
<div class="criteria"> <div class="criteria">
<div class="c-title">判断这份差异评估结论是否可信,请逐项核对</div> <div class="c-title">判断这份差异评估结论是否可信,请逐项核对</div>
<ul> <ul>
<li><b>关键参数差异均有明确状态</b>:逐项结论无漏项,每个参数都有四态之一的判定;</li> <li><b>汇总结论与明细一致</b>:结论表中每个候选的推荐程度 / 风险级别,与其下方差异明细表的四态分布吻合(存在 MISMATCH 硬约束或悬空 UNKNOWN 的候选不得给「推荐」);</li>
<li><b>关键参数差异均有明确状态</b>:每个候选的逐项结论无漏项,每个参数都有四态之一的判定;</li>
<li><b>UNKNOWN 项均有去向</b>:每个 UNKNOWN 都挂了验证任务(如 VAL-2026-0188)或补来源动作,不允许悬空;</li> <li><b>UNKNOWN 项均有去向</b>:每个 UNKNOWN 都挂了验证任务(如 VAL-2026-0188)或补来源动作,不允许悬空;</li>
<li><b>证据链完整</b>:每项结论可追溯至 P-08 / P-10 / P-06 或 EXT-02 的具体记录,或追溯至数据工单创建记录(标注:人工录入 · 数据工单号 · 确认人);</li> <li><b>证据链完整</b>:每项结论可追溯至 P-08 / P-10 / P-06 或 EXT-02 的具体记录,或追溯至数据工单创建记录(标注:人工录入 · 数据工单号 · 确认人);硬约束项未经证据支持不得判 MATCH。</li>
<li><b>硬约束项未被自动判 MATCH</b>:封装 / 工艺等硬约束未经证据支持不得判 MATCH。</li>
</ul> </ul>
</div> </div>
<p class="zone"><b>【AI 产出的差异明细】</b>—— 只读展示(P-11 差异明细子表草稿 · 一条参数一条记录 difference_item_id)</p> <p class="zone"><b>【候选 PN 结论汇总】</b>—— 每个候选一条结论,内容 = 对应下方明细表的汇总判定(P-11 主表草稿)</p>
<div class="tbl-wrap"> <div class="tbl-wrap" style="margin-bottom:20px">
<table> <table>
<thead><tr><th style="width:40px">#</th><th data-h="参数项 parameter_name">参数项 parameter_name</th><th data-h="原PN值 original_value">原 PN 值 original_value</th><th data-h="候选PN值 candidate_value">候选 PN 值 candidate_value</th><th data-h="单位 parameter_unit">单位 parameter_unit</th><th data-h="约束类型 constraint_type">约束类型 constraint_type</th><th data-h="结论 difference_status">结论 difference_status</th><th data-h="风险等级">风险等级</th><th data-h="证据引用 evidence_ref">证据引用 evidence_ref</th><th data-h="AI置信度">AI 置信度</th></tr></thead> <thead>
<tbody data-rows="out.difference_items"> <tr>
<th style="width:40px">#</th>
<th data-h="候选PN candidate_pn">候选 PN</th>
<th data-h="品牌 brand_name">品牌</th>
<th data-h="推荐程度 recommendation">推荐程度</th>
<th data-h="风险级别 risk_level">风险级别</th>
<th data-h="差异统计 diff_stats">差异统计(四态)</th>
<th data-h="结论摘要 conclusion_summary">结论摘要</th>
</tr>
</thead>
<tbody data-rows="out.candidate_conclusions">
<tr> <tr>
<td class="num">1</td> <td class="num">1</td>
<td data-field="parameter_name" class="cust">输出电压</td> <td data-field="candidate_pn" class="cust">XL2596S-5.0</td>
<td data-field="original_value" class="mono">5.0</td> <td data-field="brand_name">XLSEMI 芯龙</td>
<td data-field="candidate_value" class="mono">5.0</td> <td data-field="recommendation"><span class="rec cond">有条件推荐</span></td>
<td data-field="parameter_unit">V</td> <td data-field="risk_level"><span class="risk mid">中</span></td>
<td data-field="constraint_type">硬约束</td> <td data-field="diff_stats" class="stat"><b class="m">一致 4</b> · <b class="x">差异 1</b> · <b class="u">未知 1</b> · <b class="c">附条件 1</b></td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td> <td data-field="conclusion_summary" class="reason">电气主参数一致、工作温度覆盖更宽;使能阈值未披露需挂验证,生命周期凭供货承诺函附条件通过。</td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.98</td>
</tr> </tr>
<tr> <tr>
<td class="num">2</td> <td class="num">2</td>
<td data-field="parameter_name" class="cust">输出电流</td> <td data-field="candidate_pn" class="cust">AP1501A-5.0</td>
<td data-field="original_value" class="mono">3</td> <td data-field="brand_name">Diodes</td>
<td data-field="candidate_value" class="mono">3</td> <td data-field="recommendation"><span class="rec no">不推荐</span></td>
<td data-field="parameter_unit">A</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.97</td>
</tr>
<tr>
<td class="num">3</td>
<td data-field="parameter_name" class="cust">开关频率</td>
<td data-field="original_value" class="mono">150</td>
<td data-field="candidate_value" class="mono">150</td>
<td data-field="parameter_unit">kHz</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.95</td>
</tr>
<tr>
<td class="num">4</td>
<td data-field="parameter_name" class="cust">封装</td>
<td data-field="original_value" class="mono">TO-263-5</td>
<td data-field="candidate_value" class="mono">TO-263-5</td>
<td data-field="parameter_unit">—</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.99</td>
</tr>
<tr>
<td class="num">5</td>
<td data-field="parameter_name" class="cust">工作温度</td>
<td data-field="original_value" class="mono">-40~+85</td>
<td data-field="candidate_value" class="mono">-40~+125</td>
<td data-field="parameter_unit">℃</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st miss">MISMATCH</span></td>
<td data-field="risk_level"><span class="risk hi">高</span></td> <td data-field="risk_level"><span class="risk hi">高</span></td>
<td data-field="evidence_ref" class="mono">P-10</td> <td data-field="diff_stats" class="stat"><b class="m">一致 3</b> · <b class="x">差异 2</b> · <b class="u">未知 1</b></td>
<td data-field="confidence" class="mono">0.93</td> <td data-field="conclusion_summary" class="reason">使能逻辑与原设计相反,替换需改原理图;静态电流偏高。替换代价大于收益。</td>
</tr>
<tr>
<td class="num">6</td>
<td data-field="parameter_name" class="cust">使能阈值</td>
<td data-field="original_value" class="mono">1.4</td>
<td data-field="candidate_value" class="mono">未披露</td>
<td data-field="parameter_unit">V</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st unknown">UNKNOWN</span></td>
<td data-field="risk_level"><span class="risk mid">中</span></td>
<td data-field="evidence_ref" class="mono">挂 VAL-2026-0188</td>
<td data-field="confidence" class="mono">—</td>
</tr>
<tr>
<td class="num">7</td>
<td data-field="parameter_name" class="cust">生命周期</td>
<td data-field="original_value" class="mono">Active</td>
<td data-field="candidate_value" class="mono">Active</td>
<td data-field="parameter_unit">—</td>
<td data-field="constraint_type">软约束</td>
<td data-field="difference_status"><span class="st cond">CONDITIONAL</span></td>
<td data-field="risk_level"><span class="risk mid">中</span></td>
<td data-field="evidence_ref" class="mono">P-10(候选供货承诺函)</td>
<td data-field="confidence" class="mono">0.90</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<p class="zone"><b>【差异明细 · 按候选 PN 分组】</b>—— 每个候选一张表,与原 PN 逐项对比(P-11 差异明细子表草稿 · 一条参数一条记录 difference_item_id)</p>
<section class="cand" data-candidate="XL2596S-5.0">
<div class="cand-head">
<span class="cand-idx">1</span>
<span class="cand-name" data-field="candidate_pn">XL2596S-5.0</span>
<span class="cand-brand" data-field="brand_name">XLSEMI 芯龙</span>
<span class="cand-vs">vs 原 PN · LM2596S-5.0(TI)</span>
<span class="cand-con"><span class="rec cond" data-field="recommendation">有条件推荐</span><span class="risk mid" data-field="risk_level">风险 中</span></span>
</div>
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th style="width:40px">#</th>
<th data-h="参数项 parameter_name">参数项</th>
<th data-h="原PN值 original_value">原 PN 值</th>
<th data-h="候选PN值 candidate_value">候选 PN 值</th>
<th data-h="单位 parameter_unit">单位</th>
<th data-h="约束类型 constraint_type">约束类型</th>
<th data-h="结论 difference_status">结论</th>
<th data-h="风险等级">风险等级</th>
<th data-h="证据引用 evidence_ref">证据引用</th>
<th data-h="AI置信度">AI 置信度</th>
</tr>
</thead>
<tbody data-rows="out.difference_items">
<tr>
<td class="num">1</td>
<td data-field="parameter_name" class="cust">输出电压</td>
<td data-field="original_value" class="mono">5.0</td>
<td data-field="candidate_value" class="mono">5.0</td>
<td data-field="parameter_unit">V</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.98</td>
</tr>
<tr>
<td class="num">2</td>
<td data-field="parameter_name" class="cust">输出电流</td>
<td data-field="original_value" class="mono">3</td>
<td data-field="candidate_value" class="mono">3</td>
<td data-field="parameter_unit">A</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.97</td>
</tr>
<tr>
<td class="num">3</td>
<td data-field="parameter_name" class="cust">开关频率</td>
<td data-field="original_value" class="mono">150</td>
<td data-field="candidate_value" class="mono">150</td>
<td data-field="parameter_unit">kHz</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.95</td>
</tr>
<tr>
<td class="num">4</td>
<td data-field="parameter_name" class="cust">封装</td>
<td data-field="original_value" class="mono">TO-263-5</td>
<td data-field="candidate_value" class="mono">TO-263-5</td>
<td data-field="parameter_unit">—</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.99</td>
</tr>
<tr>
<td class="num">5</td>
<td data-field="parameter_name" class="cust">工作温度</td>
<td data-field="original_value" class="mono">-40~+85</td>
<td data-field="candidate_value" class="mono">-40~+125</td>
<td data-field="parameter_unit">℃</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st miss">MISMATCH</span></td>
<td data-field="risk_level"><span class="risk hi">高</span></td>
<td data-field="evidence_ref" class="mono">P-10</td>
<td data-field="confidence" class="mono">0.93</td>
</tr>
<tr>
<td class="num">6</td>
<td data-field="parameter_name" class="cust">使能阈值</td>
<td data-field="original_value" class="mono">1.4</td>
<td data-field="candidate_value" class="mono">未披露</td>
<td data-field="parameter_unit">V</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st unknown">UNKNOWN</span></td>
<td data-field="risk_level"><span class="risk mid">中</span></td>
<td data-field="evidence_ref" class="mono">挂 VAL-2026-0188</td>
<td data-field="confidence" class="mono">—</td>
</tr>
<tr>
<td class="num">7</td>
<td data-field="parameter_name" class="cust">生命周期</td>
<td data-field="original_value" class="mono">Active</td>
<td data-field="candidate_value" class="mono">Active</td>
<td data-field="parameter_unit">—</td>
<td data-field="constraint_type">软约束</td>
<td data-field="difference_status"><span class="st cond">CONDITIONAL</span></td>
<td data-field="risk_level"><span class="risk mid">中</span></td>
<td data-field="evidence_ref" class="mono">P-10(候选供货承诺函)</td>
<td data-field="confidence" class="mono">0.90</td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="cand" data-candidate="AP1501A-5.0">
<div class="cand-head">
<span class="cand-idx">2</span>
<span class="cand-name" data-field="candidate_pn">AP1501A-5.0</span>
<span class="cand-brand" data-field="brand_name">Diodes</span>
<span class="cand-vs">vs 原 PN · LM2596S-5.0(TI)</span>
<span class="cand-con"><span class="rec no" data-field="recommendation">不推荐</span><span class="risk hi" data-field="risk_level">风险 高</span></span>
</div>
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th style="width:40px">#</th>
<th data-h="参数项 parameter_name">参数项</th>
<th data-h="原PN值 original_value">原 PN 值</th>
<th data-h="候选PN值 candidate_value">候选 PN 值</th>
<th data-h="单位 parameter_unit">单位</th>
<th data-h="约束类型 constraint_type">约束类型</th>
<th data-h="结论 difference_status">结论</th>
<th data-h="风险等级">风险等级</th>
<th data-h="证据引用 evidence_ref">证据引用</th>
<th data-h="AI置信度">AI 置信度</th>
</tr>
</thead>
<tbody data-rows="out.difference_items">
<tr>
<td class="num">1</td>
<td data-field="parameter_name" class="cust">输出电压</td>
<td data-field="original_value" class="mono">5.0</td>
<td data-field="candidate_value" class="mono">5.0</td>
<td data-field="parameter_unit">V</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.98</td>
</tr>
<tr>
<td class="num">2</td>
<td data-field="parameter_name" class="cust">输出电流</td>
<td data-field="original_value" class="mono">3</td>
<td data-field="candidate_value" class="mono">3</td>
<td data-field="parameter_unit">A</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.96</td>
</tr>
<tr>
<td class="num">3</td>
<td data-field="parameter_name" class="cust">开关频率</td>
<td data-field="original_value" class="mono">150</td>
<td data-field="candidate_value" class="mono">150</td>
<td data-field="parameter_unit">kHz</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st ok">MATCH</span></td>
<td data-field="risk_level"><span class="risk low">低</span></td>
<td data-field="evidence_ref" class="mono">P-08 · P-10</td>
<td data-field="confidence" class="mono">0.94</td>
</tr>
<tr>
<td class="num">4</td>
<td data-field="parameter_name" class="cust">使能逻辑</td>
<td data-field="original_value" class="mono">高电平有效</td>
<td data-field="candidate_value" class="mono">低电平有效</td>
<td data-field="parameter_unit">—</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st miss">MISMATCH</span></td>
<td data-field="risk_level"><span class="risk hi">高</span></td>
<td data-field="evidence_ref" class="mono">P-10</td>
<td data-field="confidence" class="mono">0.92</td>
</tr>
<tr>
<td class="num">5</td>
<td data-field="parameter_name" class="cust">静态电流</td>
<td data-field="original_value" class="mono">10</td>
<td data-field="candidate_value" class="mono">20</td>
<td data-field="parameter_unit">mA</td>
<td data-field="constraint_type">软约束</td>
<td data-field="difference_status"><span class="st miss">MISMATCH</span></td>
<td data-field="risk_level"><span class="risk mid">中</span></td>
<td data-field="evidence_ref" class="mono">P-10</td>
<td data-field="confidence" class="mono">0.90</td>
</tr>
<tr>
<td class="num">6</td>
<td data-field="parameter_name" class="cust">使能阈值</td>
<td data-field="original_value" class="mono">1.4</td>
<td data-field="candidate_value" class="mono">未披露</td>
<td data-field="parameter_unit">V</td>
<td data-field="constraint_type">硬约束</td>
<td data-field="difference_status"><span class="st unknown">UNKNOWN</span></td>
<td data-field="risk_level"><span class="risk mid">中</span></td>
<td data-field="evidence_ref" class="mono">挂 VAL-2026-0189</td>
<td data-field="confidence" class="mono">—</td>
</tr>
</tbody>
</table>
</div>
</section>
<div class="flow"><b>审批:</b>本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。审核意见与补充条件写入 P-06(验证要求 verification_requirement);<b>通过后去向:</b>进入步骤 5 · 评审放行与落账确认。</div> <div class="flow"><b>审批:</b>本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。审核意见与补充条件写入 P-06(验证要求 verification_requirement);<b>通过后去向:</b>进入步骤 5 · 评审放行与落账确认。</div>
</div> </div>
@@ -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 记录原文,无凭空编造的测试数据。
@@ -0,0 +1,409 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ACT-WORK-05 差异评估 · 关键台账数据 · SS02 需求分析与方案匹配</title>
<style>
: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)}
/* 表格 */
.tbl-wrap{border:1px solid var(--line-soft);border-radius:10px;overflow: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)}
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}}
</style>
</head>
<body>
<div class="page">
<header class="mast">
<div class="kick">SS02 需求分析与方案匹配 · 活动台账视图</div>
<h1><span class="code">ACT-WORK-05</span>关键台账数据</h1>
<p class="sub">视角:本活动一次运行实例的读数快照 · 关联需求 <b>REQ-20260826-001</b>(客户 CUS-2025-0007 · 项目 PRJ-PSU-40V)· 评估基准 <b>LM2596S-5.0(TI)</b> → 候选 <b>XL2596S-5.0(XLSEMI)</b>、<b>LM2596S-ADJ(TI)</b> · 规则版本 <b>R-2026.07</b></p>
<div class="legend">
<span class="li"><span class="dot full"></span>台账数据文件直读(P-08 / P-06 模拟数据 V0.1)</span>
<span class="li"><span class="dot half"></span>关联回链推导(P-01 / P-10 / P-11 暂无独立数据文件,由 P-08 / P-06 的引用关系与参数集推导展示)</span>
</div>
</header>
<!-- 重要性分层 -->
<div class="layers">
<div class="layer"><b><span class="lg">P-08</span> 芯片主数据</b>核心输入 · 评估原材料,查无即硬阻塞</div>
<div class="layer core-out"><b><span class="lg">P-11</span> 替代批准关系库</b>核心产出 · 结论与明细的落账地</div>
<div class="layer evid"><b><span class="lg">P-10</span> 原厂技术资料库</b>证据链 · 每项结论的版本化出处</div>
<div class="layer loop"><b><span class="lg">P-06</span> 样品验证记录</b>闭环去向 · UNKNOWN 的实测收口</div>
<div class="layer ctx"><b><span class="lg">P-01</span> 客户需求主台账</b>上下文 · 需求源头与回写对象</div>
</div>
<!-- P-01 -->
<section id="p01">
<div class="sec-head">
<h2><span class="lid">P-01</span>客户需求主台账</h2>
<span class="ltag ctx">上下文 · R+W</span>
<span class="why">启动时新建需求;结束时回写风险 / 验证要求 / 输出链接 / 下一步 / 当前阶段</span>
<span class="cnt">回链推导 · 1 条</span>
</div>
<div class="kv">
<div class="cell"><div class="k">需求编号</div><div class="v mono">REQ-20260826-001</div></div>
<div class="cell"><div class="k">客户</div><div class="v mono">CUS-2025-0007</div></div>
<div class="cell"><div class="k">项目</div><div class="v mono">PRJ-PSU-40V</div></div>
<div class="cell"><div class="k">需求版本</div><div class="v mono">V1(启动新建)</div></div>
<div class="cell span2"><div class="k">需求描述</div><div class="v">工业电源 12V→5V 电源位替代:原 LM2596S-5.0(NRND,PCN-2025-118 通知 2027-03 停产)</div></div>
<div class="cell"><div class="k">应用场景</div><div class="v">12V/24V/40V 输入三档 · 0.5A~3A 负载 · 工业温域</div></div>
<div class="cell"><div class="k">BOM 位</div><div class="v mono">U3 电源位</div></div>
<div class="cell"><div class="k">原内部料号</div><div class="v mono">LM2596S-5.0</div></div>
<div class="cell"><div class="k">候选内部料号</div><div class="v mono">XL2596S-5.0 · LM2596S-ADJ</div></div>
<div class="cell"><div class="k">风险等级(回写)</div><div class="v"><span class="st warn">中</span></div></div>
<div class="cell"><div class="k">验证要求(回写)</div><div class="v mono">VAL-2026-0188</div></div>
<div class="cell"><div class="k">当前阶段</div><div class="v">提交 ACT-GATE-03</div></div>
<div class="cell span2"><div class="k">下一步动作</div><div class="v">提交 ACT-GATE-03 评审放行(FAE · 李工 · 1 个工作日)</div></div>
</div>
</section>
<!-- P-08 -->
<section id="p08">
<div class="sec-head">
<h2><span class="lid">P-08</span>芯片主数据台账</h2>
<span class="ltag in">核心输入 · R(查无 → 数据工单创建)</span>
<span class="why">原 PN 与候选 PN 的物料基准 + 标准化参数集,逐项比对的原材料</span>
<span class="cnt">数据文件直读 · 3 条(本活动相关)</span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr><th>内部料号</th><th>品牌 / 制造商</th><th>规格描述</th><th>封装 / 工艺</th><th>生命周期</th><th>Datasheet</th><th>标准化参数集(8 项)</th><th>适配场景</th></tr></thead>
<tbody data-rows="ledger.p08">
<tr>
<td class="pn mono">LM2596S-5.0<br><span style="font-weight:400;color:var(--ink-4)">P08-0001 · v3</span></td>
<td>TI<br><span style="color:var(--ink-4);font-size:11.5px">Texas Instruments</span></td>
<td>降压型 DCDC 稳压器,3A 输出,固定 5.0V,150kHz</td>
<td class="mono">TO-263-5<br>BCD</td>
<td><span class="st miss">NRND</span><br><span style="font-size:11px;color:var(--ink-3)">PCN-2025-118 已通知,2027-03 停止供货</span></td>
<td class="mono">Rev H(2023-09)</td>
<td><div class="pchips"><span>输入 4.5~40V</span><span>输出 5.0V 固定</span><span>电流 3A</span><span>150kHz</span><span>静态 5mA</span><span>-40~+85℃</span><span>EN 阈值 1.4V</span><span>内置反馈</span></div></td>
<td>工业电源;板载 5V 轨</td>
</tr>
<tr>
<td class="pn mono">XL2596S-5.0<br><span style="font-weight:400;color:var(--ink-4)">P08-0002 · v2</span></td>
<td>XLSEMI<br><span style="color:var(--ink-4);font-size:11.5px">XLSEMI</span></td>
<td>降压型 DCDC 稳压器,3A 输出,固定 5.0V,150kHz,与 LM2596S-5.0 引脚兼容</td>
<td class="mono">TO-263-5<br>BCD</td>
<td><span class="st ok">Active</span></td>
<td class="mono">Rev C(2025-06)</td>
<td><div class="pchips"><span>输入 4.5~40V</span><span>输出 5.0V 固定</span><span>电流 3A</span><span>150kHz</span><span>静态 4.2mA</span><span>-40~+125℃</span><span>EN 阈值 未披露</span><span>内置反馈</span></div></td>
<td>工业电源;消费电源</td>
</tr>
<tr>
<td class="pn mono">LM2596S-ADJ<br><span style="font-weight:400;color:var(--ink-4)">P08-0003 · v2</span></td>
<td>TI<br><span style="color:var(--ink-4);font-size:11.5px">Texas Instruments</span></td>
<td>降压型 DCDC 稳压器,3A,可调输出 1.23~37V,150kHz</td>
<td class="mono">TO-263-5<br>BCD</td>
<td><span class="st ok">Active</span></td>
<td class="mono">Rev H(2023-09)</td>
<td><div class="pchips"><span>输入 4.5~40V</span><span>输出 1.23~37V 可调</span><span>电流 3A</span><span>150kHz</span><span>静态 5mA</span><span>-40~+125℃</span><span>EN 阈值 1.4V</span><span>外部分压反馈</span></div></td>
<td>可调电源;工控</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- P-11 -->
<section id="p11">
<div class="sec-head">
<h2><span class="lid">P-11</span>替代选型与批准关系库 · 替代关系主记录</h2>
<span class="ltag out">核心产出 · R+W</span>
<span class="why">每个候选一条关系记录;客户批准状态为只读边界,本活动不写</span>
<span class="cnt">回链推导 · 2 条</span>
</div>
<div class="tbl-wrap" style="margin-bottom:14px">
<table>
<thead><tr><th>替代关系 ID</th><th>原 → 候选</th><th>替代类型</th><th>推荐等级</th><th>分析状态</th><th>风险</th><th>验证要求 / 回链</th><th>规则版本</th><th>客户批准(只读)</th></tr></thead>
<tbody data-rows="ledger.p11_relations">
<tr>
<td class="mono">P11-SUB-0001</td>
<td class="mono">LM2596S-5.0 → <b>XL2596S-5.0</b></td>
<td>Pin-to-Pin</td>
<td><span class="st warn">条件推荐</span></td>
<td><span class="st cond">FAE确认</span></td>
<td><span class="st warn">中</span></td>
<td class="mono">VAL-2026-0188(使能阈值实测,进行中)</td>
<td class="mono">R-2026.07</td>
<td><span class="st gray">未提交</span></td>
</tr>
<tr>
<td class="mono">P11-SUB-0002</td>
<td class="mono">LM2596S-5.0 → <b>LM2596S-ADJ</b></td>
<td>功能替代</td>
<td><span class="st cond">备选</span></td>
<td><span class="st cond">FAE确认</span></td>
<td><span class="st ok">低</span></td>
<td class="mono">VAL-2026-0040(条件通过:外部分压精度 ±2.1%)</td>
<td class="mono">R-2026.07</td>
<td><span class="st gray">未提交</span></td>
</tr>
</tbody>
</table>
</div>
<div class="sec-head" style="margin-bottom:10px">
<h2 style="font-size:13.5px"><span class="lid">P-11</span>差异明细子表(非 MATCH 项,由双方 P-08 标准化参数集逐项比对得出)</h2>
<span class="cnt">6 条</span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr><th>所属关系</th><th>参数项</th><th>原值 → 候选值</th><th>约束</th><th>结论</th><th>风险</th><th>证据引用</th><th>影响说明</th></tr></thead>
<tbody data-rows="ledger.p11_diff_items">
<tr>
<td class="mono">SUB-0001</td>
<td>生命周期</td>
<td class="mono">NRND(PCN-2025-118)→ Active</td>
<td>软约束</td>
<td><span class="st miss">MISMATCH</span></td>
<td><span class="st miss">高</span></td>
<td class="mono">P-08 · P08-0001/0002</td>
<td>原料 2027-03 停产——本次替代的驱动因素</td>
</tr>
<tr>
<td class="mono">SUB-0001</td>
<td>工作温度</td>
<td class="mono">-40~+85℃ → -40~+125℃</td>
<td>硬约束</td>
<td><span class="st miss">MISMATCH</span></td>
<td><span class="st warn">中</span></td>
<td class="mono">P-08 参数集 v2026-08</td>
<td>候选温域更宽,覆盖原需求</td>
</tr>
<tr>
<td class="mono">SUB-0001</td>
<td>使能阈值</td>
<td class="mono">1.4V → 未披露</td>
<td>硬约束</td>
<td><span class="st unknown">UNKNOWN</span></td>
<td><span class="st warn">中</span></td>
<td class="mono">挂 VAL-2026-0188</td>
<td>原厂未披露,实测闭环前维持条件推荐</td>
</tr>
<tr>
<td class="mono">SUB-0001</td>
<td>静态电流</td>
<td class="mono">5mA → 4.2mA</td>
<td>软约束</td>
<td><span class="st miss">MISMATCH</span></td>
<td><span class="st ok">低</span></td>
<td class="mono">P-08 参数集 v2026-08</td>
<td>候选更优,无不利影响</td>
</tr>
<tr>
<td class="mono">SUB-0002</td>
<td>输出电压</td>
<td class="mono">5.0V 固定 → 1.23~37V 可调</td>
<td>硬约束</td>
<td><span class="st cond">CONDITIONAL</span></td>
<td><span class="st warn">中</span></td>
<td class="mono">VAL-2026-0040</td>
<td>需外部分压设定 5.0V,精度 ±2.1%(固定版 ±1%)</td>
</tr>
<tr>
<td class="mono">SUB-0002</td>
<td>反馈网络</td>
<td class="mono">内置固定分压 → 外部分压电阻</td>
<td>硬约束</td>
<td><span class="st cond">CONDITIONAL</span></td>
<td><span class="st warn">中</span></td>
<td class="mono">VAL-2026-0040</td>
<td>BOM 增加 2 颗 0.1% 电阻,成本上升</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- P-10 -->
<section id="p10">
<div class="sec-head">
<h2><span class="lid">P-10</span>原厂技术资料库</h2>
<span class="ltag evid">证据链 · R(查无 → 数据工单创建)</span>
<span class="why">每项结论的 datasheet 出处,版本回链</span>
<span class="cnt">回链推导 · 2 条(来自 P-08 datasheet_ref)</span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr><th>关联料号</th><th>品牌</th><th>资料类型</th><th>资料文件</th><th>版本</th><th>状态</th></tr></thead>
<tbody data-rows="ledger.p10">
<tr>
<td class="mono">LM2596S-5.0 · LM2596S-ADJ</td>
<td>TI</td>
<td>Datasheet</td>
<td class="mono">ti.com/lit/ds/snvsbt0/snvsbt0.pdf</td>
<td class="mono">Rev H(2023-09)</td>
<td><span class="st ok">有效</span></td>
</tr>
<tr>
<td class="mono">XL2596S-5.0</td>
<td>XLSEMI</td>
<td>Datasheet</td>
<td class="mono">xlsemi.com/datasheet/XL2596S-5.0.pdf</td>
<td class="mono">Rev C(2025-06)</td>
<td><span class="st ok">有效</span></td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- P-06 -->
<section id="p06">
<div class="sec-head">
<h2><span class="lid">P-06</span>样品交付与芯片验证记录台账</h2>
<span class="ltag loop">闭环去向 · R + 条件写(不可创建,只能实测产生)</span>
<span class="why">UNKNOWN / CONDITIONAL 的收口处;验证完成后回链 P-11</span>
<span class="cnt">数据文件直读 · 3 条(本活动相关)</span>
</div>
<div class="tbl-wrap">
<table>
<thead><tr><th>验证 ID</th><th>关联需求 / 项目</th><th>验证料号</th><th>验证类型</th><th>测试条件</th><th>结果 / 状态</th><th>问题与风险</th><th>后续动作</th></tr></thead>
<tbody data-rows="ledger.p06">
<tr>
<td class="mono">VAL-2026-0188</td>
<td class="mono">REQ-20260826-001<br>PRJ-PSU-40V</td>
<td class="mono">XL2596S-5.0</td>
<td>Pin-to-Pin</td>
<td>样品上板:U3 电源位,输入 12V/24V/40V 三档,负载 0.5A/1.5A/3A,25℃</td>
<td><span class="st cond">进行中</span><br><span style="font-size:11px;color:var(--ink-3)">待实测,排期 2026-08-28 · 批次 XL-202607-03</span></td>
<td>使能阈值差异影响开关机逻辑判定,风险=中</td>
<td>完成 EN 阈值实测并回链 P11-SUB-0001;未闭环前维持条件推荐</td>
</tr>
<tr>
<td class="mono">VAL-2026-0040</td>
<td class="mono">REQ-20260205-001<br>—</td>
<td class="mono">LM2596S-ADJ</td>
<td>功能</td>
<td>可调输出验证:外部分压设定 5.0V,与固定版输出精度对比</td>
<td><span class="st warn">条件通过</span><br><span style="font-size:11px;color:var(--ink-3)">精度 ±2.1%(固定版 ±1%),温漂 +0.6% · RPT-2026-0040</span></td>
<td>精度敏感场景需 0.1% 电阻,BOM 成本上升,风险=低</td>
<td>作为备选结论回链 P11-SUB-0002</td>
</tr>
<tr>
<td class="mono">VAL-2026-0032</td>
<td class="mono">REQ-20260120-004<br>PRJ-PSU-40V</td>
<td class="mono">XL2596S-5.0</td>
<td>可靠性</td>
<td>高温满载:70℃ 环境、40V 输入、5V/3A 输出连续 96h</td>
<td><span class="st ok">通过</span><br><span style="font-size:11px;color:var(--ink-3)">壳温 108℃(加散热片),输出漂移 +0.8% · RPT-2026-0032</span></td>
<td>—</td>
<td>—(作为本次评估历史证据引用)</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- 次要台账 -->
<section id="minor">
<div class="sec-head">
<h2>次要台账(只读引用,不进主看板)</h2>
</div>
<div class="minor">
<div class="m"><b><span class="lid">P-09</span>客户技术与机会档案</b>提供 BOM 位(U3 电源位)与客户技术栈背景;缺口由执行工单补齐。</div>
<div class="m"><b><span class="lid">EXT-02</span>外部来源清单</b>登记本次评估用到的可信外部来源(原厂官网 datasheet 等)。</div>
<div class="m"><b><span class="lid">GOV</span>评估规则库</b>四态判定口径;本次锁定版本 R-2026.07,随结论一并落账。</div>
</div>
</section>
<footer>
<span>SS02 · ACT-WORK-05 差异评估 · 关键台账数据视图</span>
<span style="font-family:var(--mono)">数据来源:P-08-芯片主数据台账-模拟数据V0.1.xlsx · P-06-样品验证记录台账-模拟数据V0.1.xlsx · 字段口径:Demo台账字段细化与统一方案</span>
</footer>
</div>
</body>
</html>
+51 -1
View File
@@ -42,6 +42,13 @@ const E2E_CLIENT_SECRET = process.env.KEYCLOAK_E2E_CLIENT_SECRET ?? '';
* 不设它才走服务账号那条正路(ADR-012)。 * 不设它才走服务账号那条正路(ADR-012)。
*/ */
const DIRECT_BEARER = (process.env.JCAI_E2E_BEARER ?? '').replace(/^Bearer\s+/i, '').trim(); 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 */ /** access token 只活 5 分钟:超过 4 分钟就换一枚,免得长案例中途 401 */
const TOKEN_REFRESH_MS = 4 * 60 * 1000; const TOKEN_REFRESH_MS = 4 * 60 * 1000;
@@ -553,6 +560,8 @@ const SUITES = {
rowKey: 'internal_pn', rowKey: 'internal_pn',
blankCol: 'brand_name', blankCol: 'brand_name',
expectGateBlock: true, expectGateBlock: true,
// 数据工单确认即入库(2026-08-27 用户定)—— 交卷那一刻这张卡的表就该进正式表
landsOnConfirm: true,
edits: [], edits: [],
// 案例没点名的料号兜底(不该走到这里 —— 每个案例都在 stageEdits 里逐颗给了) // 案例没点名的料号兜底(不该走到这里 —— 每个案例都在 stageEdits 里逐颗给了)
fillAllBlank: { fillAllBlank: {
@@ -580,6 +589,7 @@ const SUITES = {
rowKey: 'internal_pn', rowKey: 'internal_pn',
blankCol: 'document_title', blankCol: 'document_title',
expectGateBlock: true, expectGateBlock: true,
landsOnConfirm: true,
edits: [], edits: [],
fillAllBlank: { fillAllBlank: {
doc_seq: '01', doc_seq: '01',
@@ -838,6 +848,27 @@ function draftRows(drafts, address) {
return (drafts ?? []).filter((d) => d.address === address).map((d) => ({ ...d.key, ...d.patch })); 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) { async function submitExecution(cardId) {
await api('/api/desk/work-orders/reply', { await api('/api/desk/work-orders/reply', {
@@ -1135,7 +1166,7 @@ async function runCase(suite, kase) {
data: sample.input, data: sample.input,
message: sample.message ?? '', message: sample.message ?? '',
ownerId: '', ownerId: '',
landMaster: false, landMaster: LAND_MASTER,
}), }),
}); });
const taskId = started.taskId; const taskId = started.taskId;
@@ -1147,6 +1178,8 @@ async function runCase(suite, kase) {
// 录入卡的已答名单单独记 —— 与 decided 共用会把 decideSeq 按 size 取的序号顶掉(实测:点名驳回被跳过) // 录入卡的已答名单单独记 —— 与 decided 共用会把 decideSeq 按 size 取的序号顶掉(实测:点名驳回被跳过)
const entered = new Set(); const entered = new Set();
let gateBlocked = false; let gateBlocked = false;
/** 数据工单确认了却没入库(或说入库了而表里没有)—— 收工判失败 */
let landingBroken = false;
/** 资料文件那一次检查的结论(在办中验,见下面那段注释) */ /** 资料文件那一次检查的结论(在办中验,见下面那段注释) */
let fileCheck; let fileCheck;
let gateLeaked = false; let gateLeaked = false;
@@ -1331,6 +1364,19 @@ async function runCase(suite, kase) {
} }
console.log(` 交卷执行卡:${card.title}`); console.log(` 交卷执行卡:${card.title}`);
replied.add(card.id); 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') { } else if (card.mode === 'acceptance') {
if (kase.acceptEdits && !acceptEdited) { if (kase.acceptEdits && !acceptEdited) {
acceptEdited = true; acceptEdited = true;
@@ -1379,6 +1425,10 @@ async function runCase(suite, kase) {
console.log(' ⚠️ 提交闸没拦住空手交卷 —— 判失败'); console.log(' ⚠️ 提交闸没拦住空手交卷 —— 判失败');
ok = false; ok = false;
} }
if (landingBroken) {
console.log(' ⚠️ 数据工单确认即入库那道门没兑现 —— 判失败');
ok = false;
}
// 「超限才叫人」是 A4 的要害:该叫的那一单没叫,等于底价门禁是摆设 // 「超限才叫人」是 A4 的要害:该叫的那一单没叫,等于底价门禁是摆设
if (missedRows.size > 0) { if (missedRows.size > 0) {
console.log(` ⚠️ 案例点名要填的行,整例跑完也没在件上出现过(${missedRows.size} 处:${[...missedRows].slice(0, 3).join('、')}…)—— 判失败`); console.log(` ⚠️ 案例点名要填的行,整例跑完也没在件上出现过(${missedRows.size} 处:${[...missedRows].slice(0, 3).join('、')}…)—— 判失败`);