feat(w05): 件③ 落账确认与看板重绑 —— 签之前只核对,硬约束不符逐条点名

Task 9: 新增 landing-confirm(T_release 的终局验收工单,全只读,答「这一
签下去发生什么」——落账去向/结论卡/四态分布/硬约束逐条点名/待验项/发口),
三张件此刻齐了;activity-ledger 看板的 data 声明与 HTML 一并重绑到新九
张表(P-01/P-10 两域随重写撤下),解封 3 条等待三张件齐的 skip。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
xcs
2026-08-29 20:53:23 +08:00
co-authored by Claude Opus 5
parent 04675e814d
commit 4bd1a04511
5 changed files with 379 additions and 219 deletions
@@ -104,11 +104,11 @@ describe('清单本身合规', () => {
}); });
/* /*
* SKIP(2026-08-29,Task 2 → Task 7 部分交付):`input-supplement` 已随 Task 7 补回 * 解封(2026-08-29,Task 9):`landing-confirm` 已随 Task 9 补回 —— `input-supplement`
* `assets.viewTemplates`(`VIEWS` 已更新),但 `difference-review` / `landing-confirm` * (Task 7)/`difference-review`(Task 8)/`landing-confirm`(Task 9)三张全在了,
* 两张还在等 Task 8 / 9 —— `new Set(...)` 的整体相等要三张都在才成立,继续跳过。 * `new Set(...)` 的整体相等条件此刻才第一次成立。
*/ */
it.skip('三张工单视图 + 一张运营侧看板都声明了,剧本每一步引用的 view 都在其中', () => { it('三张工单视图 + 一张运营侧看板都声明了,剧本每一步引用的 view 都在其中', () => {
expect(new Set(manifest.assets.viewTemplates.map((v) => v.name))).toEqual(new Set([...VIEWS, BOARD_VIEW])); expect(new Set(manifest.assets.viewTemplates.map((v) => v.name))).toEqual(new Set([...VIEWS, BOARD_VIEW]));
for (const step of manifest.assets.businessModel.steps as ReadonlyArray<{ view?: string }>) { 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);
@@ -378,7 +378,15 @@ describe('报告模版:编辑面与只读面各就各位', () => {
} }
}); });
/* SKIP(2026-08-29,Task 2 → Task 8/9):`difference-review` / `landing-confirm` 两张局面已撤下(新的两张由 Task 8/9 重做);它们绑的字段(recommendation_level 已只剩三档、analysis_status 已不带 tally)到时也会一并对齐 */ /*
* SKIP(2026-08-29,Task 9 复核后维持 skip):`difference-review` 那半句(第 383-385 行)
* 这次实测确已成立(与「件②」组的等价断言重复,不必再靠这条揭封);但 `landing-confirm`
* 那半句(第 386-390 行)测的 `request_status`/`current_stage` 是 `demand.request` 的列 ——
* 这个活动的清单里从没声明过 `demand.request`(见「台账声明」组「这个活动不碰 P-01 / P-10 /
* 客户档案」那条,已通过),`landing-confirm` 全只读、答的是「进哪几张表各几行」不是需求状态回写,
* 这两个字段**不属于新设计的任何一步**。这不是「还没轮到」,是测的东西这次重写里压根不存在 ——
* 一个 `it` 体绑死两半、拆不开,继续整条跳过,不强救。
*/
it.skip('枚举列只用 data-class-of 投影,不摆 ASCII 原值', () => { it.skip('枚举列只用 data-class-of 投影,不摆 ASCII 原值', () => {
expect(viewHtml('difference-review')).toContain('data-class-of="difference_status"'); expect(viewHtml('difference-review')).toContain('data-class-of="difference_status"');
expect(viewHtml('difference-review')).toContain('data-class-of="recommendation_level"'); expect(viewHtml('difference-review')).toContain('data-class-of="recommendation_level"');
@@ -395,13 +403,8 @@ describe('报告模版:编辑面与只读面各就各位', () => {
await expect(host.base.interaction.pushHtml('<div><script>x()</script></div>')).rejects.toThrow(/无脚本执行权/); await expect(host.base.interaction.pushHtml('<div><script>x()</script></div>')).rejects.toThrow(/无脚本执行权/);
}); });
/* /* 解封(2026-08-29,Task 9):`landing-confirm` 补齐,三张全能验了 —— 都走同一个 `doc()` 壳。 */
* SKIP(2026-08-29,Task 2 → Task 7 部分交付):VIEWS 现在是三张新局面的名单,`input-supplement` it('三张模版的外壳都是搬自目标件的 page/card,不再含 E04/I01 那套视觉底(.proto-root)的痕迹', () => {
* 已随 Task 7 补回、这一条本身可以对它验(外壳确实是 page/card),但 `difference-review` /
* `landing-confirm` 两张还没有(viewHtml 返回空串,`toContain('class="card-head"')` 会假),
* 循环体没法只验一部分 —— 等 Task 8/9 补齐三张再一起解封。
*/
it.skip('三张模版的外壳都是搬自目标件的 page/card,不再含 E04/I01 那套视觉底(.proto-root)的痕迹', () => {
for (const view of VIEWS) { for (const view of VIEWS) {
const html = viewHtml(view); const html = viewHtml(view);
expect(html, view).toContain('class="card-head"'); expect(html, view).toContain('class="card-head"');
@@ -549,6 +552,68 @@ describe('件② 差异评估结论审核', () => {
}); });
}); });
/*
* 件③ 落账确认(2026-08-29,Task 9):T_release 的终局验收工单,`view: 'landing-confirm'` 指向它。
* **全只读,答「这一签下去发生什么」(spec §6.3)**:进哪几张表各几行(摆人读名)· 结论卡
* (等级 + 一句话)· 四态分布并把硬约束不符的逐条点名 · 待验项(P-06 的测试条件与后续动作)·
* 发哪一口。复用文件顶部已有的 `viewHtml` 短写。
*/
describe('件③ 落账确认', () => {
const html = viewHtml('landing-confirm');
it('在 viewTemplates 里,T_release 指向它', () => {
expect(html).not.toBe('');
expect((manifest.assets.businessModel.steps as ReadonlyArray<{ item: string; view?: string }>)
.find((s) => s.item === 'T_release')?.view).toBe('landing-confirm');
});
it('**全只读** —— 签之前只核对,不改', () => {
expect(html).not.toContain('data-edit=');
expect(html).not.toContain('data-col=');
expect(html).not.toContain('<script');
});
it('把「进哪几张表各几行」摆出来,用的是基座读数', () => {
for (const reading of ['substitution_relation_rows', 'substitution_difference_item_rows', 'validation_record_rows']) {
expect(html).toContain(`data-slot="${reading}"`);
}
});
it('硬约束不符的逐条点名 —— 签字的人最该看见的就是它', () => {
expect(html).toContain('data-rows="mine:substitution.difference_item"');
/*
* **筛选交给样式表,不是 `data-by`** —— `data-by` 过滤的是「子列 = 外层那一行的父列」,
* 这里没有外层行可依。做法:整表逐行渲出来,行上挂 `data-class-of="judgement_class"`,
* 由 CSS 只显 `.v-hard_mismatch` 那几行(判断留在样式表,模版不判)。
*/
expect(html).toContain('data-class-of="judgement_class"');
});
/*
* `judgement_class` 的 tally 值 `hard_mismatch` 带下划线 —— `classToken`/`vtok` 只留
* `[a-z0-9-]`(**吃掉下划线**),真正生效的选择器是 `.v-hardmismatch` 不是手写直觉的
* `.v-hard_mismatch`(2026-08-27 已在这个包里踩过一次同款事故,`vtok` 是这条回归网)。
*/
it('过滤用的选择器是从值算出来的(vtok),不是手写的 —— 不许踩「带下划线的值显示成待定」那个坑', () => {
expect(html).not.toContain('.v-hard_mismatch');
expect(html).toContain('.v-hardmismatch');
});
});
describe('运营中心看板', () => {
const board = manifest.assets.viewTemplates.find((v) => v.name === 'activity-ledger');
it('取数声明改绑新表,不再引已退役的那几张', () => {
const json = JSON.stringify(board?.data ?? {});
for (const gone of ['demand.request', 'parts.document', 'sub_eval.pn_check', 'sub_eval.input_gap', 'sub_eval.verdict']) {
expect(json).not.toContain(gone);
}
for (const kept of ['sub_eval.case', 'substitution.relation', 'substitution.difference_item']) {
expect(json).toContain(kept);
}
});
});
/* /*
* 2026-08-27 实测账,两条都真截图 + SQL 复核过: * 2026-08-27 实测账,两条都真截图 + SQL 复核过:
* ① **带下划线的枚举值一律显示成「待定」**——平台有两套互相打架的消毒规则: * ① **带下划线的枚举值一律显示成「待定」**——平台有两套互相打架的消毒规则:
@@ -561,10 +626,15 @@ describe('件② 差异评估结论审核', () => {
* 静态 `class="req"` 把整列都标了必填。 * 静态 `class="req"` 把整列都标了必填。
*/ */
/* /*
* SKIP(2026-08-29,Task 2 → Task 7-9 补回):`BADGED` 列表里的每一项都指着已撤下的五张局面 * SKIP(2026-08-29,Task 9 复核后维持 skip):三张新件与看板全部到位后复查过 ——
* (p08-master-create 等)或已改动的字段(recommendation_level 掉了 alternate 档、 * `BADGED` 列表 35 条里没有一条能原样救活:半数指着已彻底撤下的局面(`p08-master-create` /
* analysis_status 不再带 tally、`sub_eval.pn_check` 整个改名 `sub_eval.part`)。 * `eval-input-list`,Task 7 范围内已不存在)或已退役的字段(`substitution_type` 整列退休、
* 这整组「回归网」要等 Task 7–9 把新三张件补回来、字段清单也重新对齐后才谈得上继续钉。 * `${D}.pn_check` 整个改名 `${D}.part`、`demand.request`/`parts.document` 两域这次重写
* 本活动已不碰),另一半虽然指向真实存在的 view/字段(`difference-review`/`landing-confirm`
* 的 `recommendation_level`/`risk_level`/`difference_status`/`constraint_type` 等),
* 但混在同一个数组里没法只救活这几行 —— 拆开重写等于另起一份测试,超出「回归网」这条 skip
* 本该做的事。这几条真正要钉的口径,「件②」「件③」两组已经用**显式点名**的断言钉过一遍
* (逐条明细六列只读 / judgement_class 的 vtok 选择器),不必靠这条泛化清单再验一遍。
*/ */
describe.skip('徽标别名与值域相关的坑(2026-08-27 实测账,回归网)', () => { describe.skip('徽标别名与值域相关的坑(2026-08-27 实测账,回归网)', () => {
/** 必须与 `apps/api/src/modules/interaction/internal/domain/view-render.ts` 的 `classToken` 一字不差 */ /** 必须与 `apps/api/src/modules/interaction/internal/domain/view-render.ts` 的 `classToken` 一字不差 */
@@ -773,13 +843,13 @@ describe('activity-ledger:关键台账数据看板', () => {
const declaredAddresses = new Set(manifest.declarations.ledger.map((l) => `${l.domain}.${l.object}`)); const declaredAddresses = new Set(manifest.declarations.ledger.map((l) => `${l.domain}.${l.object}`));
/* /*
* SKIP(2026-08-29,Task 2 → Task 9 补回):看板的 `data` 声明(`views/ledger-board.ts` 的 * 解封(2026-08-29,Task 9「运营中心看板重绑」):`LEDGER_BOARD_DATA` 已换成新九张表 ——
* `LEDGER_BOARD_DATA`)还圈着已退休的旧台账(`sub_eval.pn_check` / `sub_eval.verdict` / * 案卷两张(`sub_eval.case`/`sub_eval.part`)+ 字段字典两张(`parts.chip_type`/`parts.type_param`)+
* `demand.request` / `customer.profile` / `parts.document`)—— Task 9「运营中心看板重绑」 * 参数对照两张(`parts.profile`/`parts.parameter`)+ 明细/关系(`substitution.difference_item`/
* 会把它换成新九张表;这条取数声明的自洽性检查到那时再验。html 自身的结构测试(下面三条) * `substitution.relation`)+ P-06(`validation.record`,含同料号历史 `validation.history`),
* 不依赖 `declarations.ledger`,仍然有效、继续跑。 * 这条取数声明的自洽性检查此刻才第一次能验。
*/ */
it.skip('取数声明:案号台账是 record 且 key 是它的 caseKey、前缀 W05-;每条 rows 的表都已声明;from 只指 case 或前面的 as.列', () => { it('取数声明:案号台账是 record 且 key 是它的 caseKey、前缀 W05-;每条 rows 的表都已声明;from 只指 case 或前面的 as.列', () => {
expect(data).toBeDefined(); expect(data).toBeDefined();
if (data === undefined) return; if (data === undefined) return;
const caseLedger = manifest.declarations.ledger.find((l) => `${l.domain}.${l.object}` === data.case.ledger); const caseLedger = manifest.declarations.ledger.find((l) => `${l.domain}.${l.object}` === data.case.ledger);
@@ -803,18 +873,23 @@ describe('activity-ledger:关键台账数据看板', () => {
} }
seen.add(entry.as); seen.add(entry.as);
} }
// 六张主表 + 案号两张 + 同料号历史都圈了 // 案卷两张 + 字段字典两张 + 参数对照两张 + 明细/关系 + P-06(含历史)都圈了
expect([...seen]).toEqual( 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']), expect.arrayContaining([`${D}.case`, `${D}.part`, 'parts.chip_type', 'parts.type_param', 'parts.profile', 'parts.parameter', 'substitution.relation', 'substitution.difference_item', 'validation.record', 'validation.history']),
); );
}); });
it('html 里每个 data-rows 键都是声明里的一个 as(行由基座圈过,不是整表);嵌套只用 data-by 父键过滤', () => { it('html 里每个 data-rows 键都是声明里的一个 as(行由基座圈过,不是整表);嵌套只用 data-by 父键过滤', () => {
const as = new Set(data?.rows.map((r) => r.as) ?? []); const as = new Set(data?.rows.map((r) => r.as) ?? []);
const keys = [...html.matchAll(/data-rows="([^"]+)"/g)].map((m) => m[1] ?? ''); const keys = [...html.matchAll(/data-rows="([^"]+)"/g)].map((m) => m[1] ?? '');
expect(keys.length).toBeGreaterThan(8); /*
* 2026-08-29 Task 9:门槛从「> 8」(旧六张主表设计)降到「> 6」——新设计收成 8 处绑定
* (P-08 主表 + 两处嵌套查表 / 字段字典 / 关系 / 明细 / P-06 本单 + 历史),门槛仍要求
* 「不止一两处」这条哨兵语义,只是数字跟着新设计的真实形状改小,不是放水。
*/
expect(keys.length).toBeGreaterThan(6);
for (const key of keys) expect(as.has(key), key).toBe(true); 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(`<span class="pnrow" data-rows="${D}.part" data-by="internal_pn">`);
expect(html).toContain('<div class="pchips" data-rows="parts.parameter" 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).toContain('<tbody class="hist" data-rows="validation.history">');
expect(html).not.toContain('mine:'); expect(html).not.toContain('mine:');
@@ -833,15 +908,24 @@ describe('activity-ledger:关键台账数据看板', () => {
expect(html).not.toContain('data-col='); 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); * 2026-08-29 Task 9 改窄:六张表收成新九张(P-01/P-10 两张连同「关联需求」「关联料号」
* 两列一起退休 —— 本活动已不碰 demand.request / parts.document);案号台账从 `${D}.verdict`
* 改名 `${D}.case`;「放弃的候选」这一概念随多候选并行设计一起退役(新设计一次评估恰一对一
* 候选,没有「本轮放弃的候选」这件事)。补一条**已退役地址不再出现**的回归网,同「运营中心看板」组同款纪律。
*/
it('新九张表的表头与页头要素都在;MATCH 行淡显 —— 都是 class 交样式表,模版不判', () => {
for (const header of ['<th>内部料号</th>', '<th>替代关系 ID</th>', '<th>参数项</th>', '<th>验证 ID</th>', '<th>标准化参数集</th>', '<th>参数名</th>']) expect(html).toContain(header);
expect(html).toContain('data-slot="case:instanceNo"'); expect(html).toContain('data-slot="case:instanceNo"');
expect(html).toContain('data-slot="case:ref"'); expect(html).toContain('data-slot="case:ref"');
expect(html).toContain(`data-slot="${D}.verdict.original_internal_pn"`); expect(html).toContain('data-slot="substitution.relation.original_internal_pn"');
expect(html).toContain('data-slot="demand.request.request_id"'); expect(html).toContain(`data-slot="${D}.case.summary"`);
expect(html).toContain('<tr data-class-of="difference_status">'); expect(html).toContain('<tr data-class-of="difference_status">');
expect(html).toContain('tr.v-match td{opacity:.55}'); 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"'); expect(html).toContain('data-slot="substitution_difference_item_difference_status_unknown"');
// 已退役的旧域 / 旧字段一律不出现
for (const gone of ['demand.request', 'parts.document', `${D}.pn_check`, `${D}.verdict`, 'substitution_type', 'customer_approval', 'v-abandoned']) {
expect(html, gone).not.toContain(gone);
}
}); });
}); });
@@ -616,6 +616,10 @@ export const ASSESSMENT_STEPS = [
item: 'T_release' as const, item: 'T_release' as const,
intent: '把这一签下去会落哪几张表、几行摆清楚,人签字后落账并发口', intent: '把这一签下去会落哪几张表、几行摆清楚,人签字后落账并发口',
guide: '', guide: '',
/* Task 9:落账确认用的终局验收工单 —— 全只读,答「这一签下去发生什么」(spec §6.3):
进哪几张表各几行 / 结论卡(等级 + 一句话)/ 四态分布并把硬约束不符逐条点名 /
待验项(P-06 的测试条件与后续动作)/ 发哪一口。 */
view: 'landing-confirm',
/* 人工验收清单:一条一句他拿着这份工单要**亲自核**的动作,不是系统判的判据 */ /* 人工验收清单:一条一句他拿着这份工单要**亲自核**的动作,不是系统判的判据 */
acceptanceChecklist: [ acceptanceChecklist: [
'推荐等级与四态分布对得上:有硬约束不符的没给「推荐」', '推荐等级与四态分布对得上:有硬约束不符的没给「推荐」',
@@ -39,7 +39,7 @@ import type { ActivityManifest } from '@contracts';
import { activityIdSchema } from '@contracts'; import { activityIdSchema } from '@contracts';
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 { LEDGER_BOARD, LEDGER_BOARD_DATA } from './views/ledger-board.ts'; import { LEDGER_BOARD, LEDGER_BOARD_DATA } from './views/ledger-board.ts';
import { INPUT_SUPPLEMENT, DIFFERENCE_REVIEW } from './views/report.ts'; import { INPUT_SUPPLEMENT, DIFFERENCE_REVIEW, LANDING_CONFIRM } from './views/report.ts';
import { ACTIVITY_ID_TEXT, D } from './constants.ts'; import { ACTIVITY_ID_TEXT, D } from './constants.ts';
export { D, RULE_VERSION } from './constants.ts'; export { D, RULE_VERSION } from './constants.ts';
@@ -442,15 +442,18 @@ export const manifest: ActivityManifest = {
* `input-supplement`(评估输入补全,S1 的数据工单,三块竖排 —— 存在性与主数据 / * `input-supplement`(评估输入补全,S1 的数据工单,三块竖排 —— 存在性与主数据 /
* 芯片类型字段 / 参数值)。**Task 8 已补回第二张**:`difference-review`(差异评估结论审核, * 芯片类型字段 / 参数值)。**Task 8 已补回第二张**:`difference-review`(差异评估结论审核,
* S3 的验收工单 —— 对决头 + 四态分布带 + 推荐等级大徽章只读,逐条明细可就地改四态 / * S3 的验收工单 —— 对决头 + 四态分布带 + 推荐等级大徽章只读,逐条明细可就地改四态 /
* 影响说明 / 判断依据 / UNKNOWN 去向,收口四格只读)。`landing-confirm` 仍待 Task 9 加回; * 影响说明 / 判断依据 / UNKNOWN 去向,收口四格只读)。**Task 9 已补回第三张**:
* 中途(Task 8–Task 9 之间)包不装,不会被静态审拦下。看板自己的 `data` 声明现在也引着 * `landing-confirm`(落账确认,T_release 的终局验收工单 —— 全只读,答「这一签下去发生
* 已经不存在的台账地址(`sub_eval.pn_check` 等)——Task 9「运营中心看板重绑」会把它换成 * 什么」:进哪几张表各几行 / 结论卡 / 四态分布并把硬约束不符逐条点名 / 待验项 / 发哪一口)。
* 新九张表。 * 三张件此刻齐了。看板自己的 `data` 声明(`LEDGER_BOARD_DATA`)同批换成了新九张表 ——
* `sub_eval.pn_check`/`sub_eval.verdict`/`demand.request`/`customer.profile`/
* `parts.document` 这五张已退役地址不再出现。
*/ */
viewTemplates: [ viewTemplates: [
{ name: 'activity-ledger', html: LEDGER_BOARD, data: LEDGER_BOARD_DATA }, { name: 'activity-ledger', html: LEDGER_BOARD, data: LEDGER_BOARD_DATA },
{ name: 'input-supplement', html: INPUT_SUPPLEMENT }, { name: 'input-supplement', html: INPUT_SUPPLEMENT },
{ name: 'difference-review', html: DIFFERENCE_REVIEW }, { name: 'difference-review', html: DIFFERENCE_REVIEW },
{ name: 'landing-confirm', html: LANDING_CONFIRM },
], ],
cardSchemas: {}, cardSchemas: {},
/* 蒸馏钩子撤空:它是给 agent 在终态回看轨迹用的,而这个活动全程无 agent(AI 只出场一次、答一份结构化判断,没有可供归纳的多轮轨迹) */ /* 蒸馏钩子撤空:它是给 agent 在终态回看轨迹用的,而这个活动全程无 agent(AI 只出场一次、答一份结构化判断,没有可供归纳的多轮轨迹) */
@@ -1,52 +1,74 @@
/** /**
* 运营中心 L2 沙箱视图 —— ACT-WORK-05「关键台账数据」看板(2026-08-27)。 * 运营中心 L2 沙箱视图 —— ACT-WORK-05「关键台账数据」看板(2026-08-27 初版,2026-08-29 Task 9 重绑)。
* *
* 版式与样式逐字节搬自 `docs/ss02-act-05/ACT-WORK-05-关键台账数据.html`(只改两处:`.tbl-wrap` 放开横滑; * **2026-08-29 Task 9「运营中心看板重绑」**:2026-08-29 整体重写(spec
* 徽标文案由 `data-class-of` 投影 + 生成式 `::after` 出,不再写死)。溯源口径照 `…-数据溯源.md`: * `docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md`)把九步案例模型砍成四步、
* 页头 / P-01 一条 / P-08 逐颗 + 参数瓷贴(一格一列,不叠灰字次列 —— 2026-08-27 用户定)/ P-11 主记录 + 差异明细 / P-10(同样一格一列)/ P-06(本单 + 同料号历史)。 * 台账从旧的 `sub_eval.pn_check`/`sub_eval.verdict` 三张单据 + `demand.request`/`customer.profile`/
* `parts.document` 五张外域表,换成本活动自持的 `sub_eval.case`/`sub_eval.part` 两张 + 新增的
* `parts.chip_type`/`parts.type_param` 两张类型字典 —— 这份看板原样引着已退役的五张地址,
* 装包会被静态审拦下。本轮把 `LEDGER_BOARD_DATA` 与 `LEDGER_BOARD` 一并**重绑**到新九张表:
* 案卷(`sub_eval.case`)→ 两颗料(`sub_eval.part`)→ 类型与字段字典(`parts.chip_type`/
* `parts.type_param`)→ 参数对照(`parts.profile`/`parts.parameter`)→ 关系与明细
* (`substitution.relation`/`substitution.difference_item`)→ P-06(`validation.record`,
* 含同料号历史 `validation.history`)。P-01 需求主台账 / P-10 原厂资料两个板块随之整段撤下 ——
* 本活动这次重写已经不碰这两张表(见 manifest.ts「这个活动不碰 P-01 / P-10 / 客户档案」)。
* 新设计是**一次评估恰一对一候选**(不再是「多候选,部分放弃」),`substitution.relation`
* 一单一行、`sub_eval.part` 一单两行(原/候选各一),「候选」不再是需要 `data-rows` 逐个复制的
* 集合,页头 / P-08 两处原来的 `.cands`(多候选并排)、`.aband`(放弃候选的候选行)两个概念
* 一并撤下 —— 新模型里没有「本轮放弃的候选」这件事。
*
* 版式与样式**沿用**(重绑不是重做):页头 / 数据来源图例 / 分层图例 / 表格样式 / 三态横幅
* 这几段版面语言原样保留;`.tbl-wrap` 放开横滑、徽标文案由 `data-class-of` 投影 + 生成式
* `::after` 出的既有约定不变。溯源口径参照 `…-数据溯源.md`(该文档写在旧九步年代,
* 域名对照以本文件为准)。
* *
* **它摆的是最近一次已放行实例的落账快照**(用户定):行由基座按 `LEDGER_BOARD_DATA` 的声明从**正式表**圈出 —— * **它摆的是最近一次已放行实例的落账快照**(用户定):行由基座按 `LEDGER_BOARD_DATA` 的声明从**正式表**圈出 ——
* 每个 `data-rows` 键都是声明里的一个 `as`,不是整表;`<as>.<列>` 是那一集合首行的标量,`<as 去符号>_rows` / * 每个 `data-rows` 键都是声明里的一个 `as`,不是整表;`<as>.<列>` 是那一集合首行的标量,`<as 去符号>_rows` /
* `_<列>_<值>` 是基座数出来的读数(`apps/api/src/app/activity-ops/view-scope.ts`)。模版仍**不算不判**: * `_<列>_<值>` 是基座数出来的读数(`apps/api/src/app/activity-ops/view-scope.ts`)。模版仍**不算不判**:
* MATCH 行淡显、放弃的候选、三态横幅,全是 `data-class-of` 投出的 class 交样式表。 * MATCH 行淡显、三态横幅,全是 `data-class-of` 投出的 class 交样式表。
* 无脚本、无外链(过安装静态审);报高脚本由服务端出口注入。 * 无脚本、无外链(过安装静态审);报高脚本由服务端出口注入。
*/ */
import type { ViewData } from '@contracts'; import type { ViewData } from '@contracts';
import { badgeCss } from './badge.ts'; import { badgeCss } from './badge.ts';
import { D } from '../constants.ts'; import { D } from '../constants.ts';
/** 案号 `W05-<开案时刻 14 位>`(与说明书让模型铸 `eval_ref` 的写法同源);行集按案号 → 核对表 → 料号 / 需求逐表圈 */ /**
* 案号 `W05-<开案时刻 14 位>`(与说明书让模型铸 `eval_ref` 的写法同源);行集按案号 → 案卷 →
* 两颗料 → 类型字典(按案卷锁定的 `chip_type_id` 查)→ 主数据 / 参数(按两颗料的 `internal_pn` 查)→
* 关系(按案号查,一单一行)→ 明细(按关系的主键查,`by.from` 必须指向**前面**已求出的集合 ——
* 明细依赖关系的主键,所以 `rows` 数组里关系排在明细**之前**,即便「关系」是这一签最终的结论、
* 「明细」是支撑结论的逐条证据,版面上想先摆哪个都行,数据依赖顺序是另一件事)→ P-06(按案号查,
* 一单固定一条;同料号历史另开一集,按两颗料的 `internal_pn` 查、排除本单自己那一条)。
*/
export const LEDGER_BOARD_DATA: ViewData = { export const LEDGER_BOARD_DATA: ViewData = {
case: { ledger: `${D}.verdict`, key: 'eval_ref', prefix: 'W05-' }, case: { ledger: `${D}.case`, key: 'eval_ref', prefix: 'W05-' },
rows: [ rows: [
{ as: `${D}.verdict`, ledger: `${D}.verdict`, by: { column: 'eval_ref', from: 'case' } }, { as: `${D}.case`, ledger: `${D}.case`, by: { column: 'eval_ref', from: 'case' } },
/* 原 PN 排第一(original > candidate),下游按料号圈的表都随这个顺序 */ { as: `${D}.part`, ledger: `${D}.part`, by: { column: 'eval_ref', from: 'case' }, orderBy: [{ field: 'role', dir: 'desc' }, { field: 'internal_pn', dir: 'asc' }] },
{ 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: 'parts.chip_type', ledger: 'parts.chip_type', by: { column: 'type_id', from: `${D}.case.chip_type_id` } },
{ as: 'demand.request', ledger: 'demand.request', by: { column: 'request_id', from: `${D}.verdict.request_id` } }, { as: 'parts.type_param', ledger: 'parts.type_param', by: { column: 'type_id', from: `${D}.case.chip_type_id` }, orderBy: [{ field: 'param_name', dir: 'asc' }] },
{ 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}.part.internal_pn` } },
{ 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}.part.internal_pn` }, orderBy: [{ field: 'param_name', dir: 'asc' }] },
{ as: 'parts.parameter', ledger: 'parts.parameter', by: { column: 'internal_pn', from: `${D}.pn_check.internal_pn` }, orderBy: [{ field: 'param_name', dir: 'asc' }] }, { as: 'substitution.relation', ledger: 'substitution.relation', by: { column: 'eval_ref', from: 'case' } },
{ 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', as: 'substitution.difference_item',
ledger: 'substitution.difference_item', ledger: 'substitution.difference_item',
by: { column: 'substitution_relation_id', from: 'substitution.relation.substitution_relation_id' }, by: { column: 'substitution_relation_id', from: 'substitution.relation.substitution_relation_id' },
orderBy: [{ field: 'difference_item_id', dir: 'asc' }], 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' }] }, { as: 'validation.record', ledger: 'validation.record', by: { column: 'eval_ref', from: 'case' } },
/* 同料号在别单 / 别处留下的验证记录:本次评估的历史证据引用(目标件 P-06 里 VAL-2026-0040 / 0032 那两行的位置) */ /* 同料号在别单 / 别处留下的验证记录:本次评估的历史证据引用,排除本单自己那一条 */
{ {
as: 'validation.history', as: 'validation.history',
ledger: 'validation.record', ledger: 'validation.record',
by: { column: 'validation_internal_pn', from: `${D}.pn_check.internal_pn` }, by: { column: 'validation_internal_pn', from: `${D}.part.internal_pn` },
not: { column: 'request_id', from: `${D}.verdict.request_id` }, not: { column: 'eval_ref', from: 'case' },
orderBy: [{ field: 'validation_id', dir: 'desc' }], orderBy: [{ field: 'validation_id', dir: 'desc' }],
}, },
], ],
}; };
/* ==================== 目标件样式(逐字节搬;两处改动标了 [改]) ==================== */ /* ==================== 目标件样式(基座壳与版面语言沿用旧版;域名相关内容按新九张表改写) ==================== */
const TARGET_CSS = ` const TARGET_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;
@@ -80,15 +102,14 @@ body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,
.dot.full{background:var(--brand)} .dot.full{background:var(--brand)}
.dot.half{background:var(--amber)} .dot.half{background:var(--amber)}
/* 分层图例 */ /* 分层图例 —— 新九张表收成四层(P-01/P-10 两层随重写撤下) */
.layers{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin:14px 0 18px} .layers{display:grid;grid-template-columns:repeat(4,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{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 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 .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.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.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.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} section{background:#fff;border:1px solid var(--line-soft);border-radius:14px;padding:18px 22px;margin-bottom:16px}
@@ -100,11 +121,10 @@ section{background:#fff;border:1px solid var(--line-soft);border-radius:14px;pad
.ltag.out{color:var(--ledger);background:var(--ledger-soft);border-color:var(--ledger-line)} .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.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.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 .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)} .sec-head .cnt{margin-left:auto;font-size:11.5px;color:var(--ink-4);font-family:var(--mono)}
/* 表格 —— [改] 目标件写 overflow:hidden,列一多右边被裁且滑不动;本仓 2026-08-23 定:表宽超出就得能横滑 */ /* 表格 —— 表宽超出就得能横滑(本仓 2026-08-23 定) */
.tbl-wrap{border:1px solid var(--line-soft);border-radius:10px;overflow-x:auto;overflow-y:hidden} .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; } .jc-tbl-wrap, .tbl-wrap { overflow-x: auto; overflow-y: hidden; }
table{width:100%;border-collapse:collapse;font-size:12.5px} table{width:100%;border-collapse:collapse;font-size:12.5px}
@@ -115,6 +135,7 @@ tbody tr{transition:background .15s}
tbody tr:hover{background:var(--brand-wash)} tbody tr:hover{background:var(--brand-wash)}
.mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)} .mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)}
.pn{font-weight:700} .pn{font-weight:700}
.cust{font-weight:700}
/* 状态 pill */ /* 状态 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{display:inline-block;padding:1px 9px;border-radius:999px;font-size:11px;font-weight:800;white-space:nowrap;border:1px solid transparent}
@@ -125,13 +146,6 @@ tbody tr:hover{background:var(--brand-wash)}
.st.gray{color:var(--ink-3);background:var(--soft);border-color:var(--line)} .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)} .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 */ /* 参数集摘要 chips */
.pchips{display:flex;flex-wrap:wrap;gap:4px} .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} .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}
@@ -143,48 +157,40 @@ tbody tr:hover{background:var(--brand-wash)}
.minor .m .lid{font-family:var(--mono);color:var(--ink-4);font-weight:700;margin-right:6px} .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} 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}} @media(max-width:900px){.layers{grid-template-columns:repeat(2,1fr)}}
`; `;
/* ==================== 绑定层:徽标别名 / 三态横幅 / 淡显 —— 只取值,不算不判 ==================== */ /* ==================== 绑定层:徽标别名 —— 只取值,不算不判 ==================== */
const OK = 'color:var(--ok);background:var(--ok-soft);border-color:#bfe6b4;'; 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 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 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 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 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 = ` const BIND_CSS = `
/* 徽标:值域各归各族(同一个词在不同列意思不同,如 pending 在资料状态是待生效、在需求状态是待处理), /* 徽标:值域各归各族(同一个词在不同列意思不同),文案与颜色由 data-class-of 投出的 v-<token> 决定;
文案与颜色由 data-class-of 投出的 v-<token> 决定;没投出 class 的格子摆「—」,客户批准那格摆「未提交」 */ 没投出 class 的格子摆「—」 */
.st::after{content:"—"} .st::after{content:"—"}
.st.ap::after{content:"未提交"}
${badgeCss('.st.lc', [ ${badgeCss('.st.lc', [
['active', 'Active', OK], ['active', 'Active', OK],
['nrnd', 'NRND', MISS], ['nrnd', 'NRND', MISS],
['eol', 'EOL', MISS], ['eol', 'EOL', MISS],
['discontinued', '停产', MISS], ['discontinued', '停产', MISS],
['risk', '风险', WARN], ['risk', '风险', UNKNOWN],
])} ])}
${badgeCss('.st.rec', [ ${badgeCss('.st.rec', [
['recommended', '推荐', OK], ['recommended', '推荐', OK],
['conditional', '条件推荐', WARN], ['conditional', '条件推荐', UNKNOWN],
['alternate', '备选', COND],
['not_recommended', '不推荐', MISS], ['not_recommended', '不推荐', MISS],
])} ])}
/* analysis_status 这次重写没有再声明 tally(本活动只写这一个固定值,分析层与客户批准层分离) */
${badgeCss('.st.an', [ ${badgeCss('.st.an', [
['ai_draft', 'AI 草稿', GRAY], ['fae_confirmed', 'FAE已确认', COND],
['fae_confirmed', 'FAE确认', COND],
['pending_customer', '待客户验证', WARN],
['customer_approved', '客户已批准', OK],
['rejected', '不批准', MISS],
['void', '作废', GRAY],
])} ])}
${badgeCss('.st.rk', [ ${badgeCss('.st.rk', [
['low', '低', OK], ['low', '低', OK],
['medium', '中', WARN], ['medium', '中', UNKNOWN],
['high', '高', MISS], ['high', '高', MISS],
['critical', '危急', MISS],
])} ])}
${badgeCss('.st.dst', [ ${badgeCss('.st.dst', [
['match', 'MATCH', OK], ['match', 'MATCH', OK],
@@ -192,36 +198,15 @@ ${badgeCss('.st.dst', [
['unknown', 'UNKNOWN', UNKNOWN], ['unknown', 'UNKNOWN', UNKNOWN],
['conditional', 'CONDITIONAL', COND], ['conditional', 'CONDITIONAL', COND],
])} ])}
/* validation_status 同样没有声明 tally(本活动只写 not_started —— 验证是否通过不是这个活动的事) */
${badgeCss('.st.vs', [ ${badgeCss('.st.vs', [
['not_started', '未开始', GRAY], ['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],
])} ])}
/* 在库状态(sub_eval.part.exists_status)—— 这次重写只剩两档(found/absent),
「本单建档」「已放弃」两档随旧的多候选设计一起退役 */
${badgeCss('.st.ex', [ ${badgeCss('.st.ex', [
['found', '在库直读', COND], ['found', '在库直读', COND],
['created', '本单建档', OK],
['absent', '缺库', MISS], ['absent', '缺库', MISS],
['abandoned', '已放弃', GRAY],
])} ])}
${badgeCss('.st.rl', [ ${badgeCss('.st.rl', [
['original', '原 PN', COND], ['original', '原 PN', COND],
@@ -229,36 +214,20 @@ ${badgeCss('.st.rl', [
])} ])}
/* 纯文字的枚举列:只补文案 */ /* 纯文字的枚举列:只补文案 */
.enumtxt::after{content:"—"} .enumtxt::after{content:"—"}
${badgeCss('.enumtxt.ty', [
['pin_to_pin', 'Pin-to-Pin'],
['functional', '功能替代'],
['parametric', '参数替代'],
['other', '其他'],
])}
${badgeCss('.enumtxt.con', [ ${badgeCss('.enumtxt.con', [
['hard', '硬约束'], ['hard', '硬约束'],
['soft', '软约束'], ['soft', '软约束'],
])} ])}
/* validation_type 这次重写多了 parametric 一档(S2 的验证记录投影固定写它) */
${badgeCss('.enumtxt.vt', [ ${badgeCss('.enumtxt.vt', [
['pin_to_pin', 'Pin-to-Pin'], ['pin_to_pin', 'Pin-to-Pin'],
['functional', '功能'], ['functional', '功能'],
['reliability', '可靠性'], ['reliability', '可靠性'],
['certification', '认证'], ['certification', '认证'],
['retest', '复测'], ['retest', '复测'],
])} ['parametric', '参数验证'],
${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 i{font-style:normal;color:var(--ink-4);margin-right:3px}
.pchips b{font-weight:700} .pchips b{font-weight:700}
@@ -269,12 +238,6 @@ ${badgeCss('.enumtxt.dt', [
.pnrow .st{font-size:10px;padding:0 7px} .pnrow .st{font-size:10px;padding:0 7px}
/* 差异明细:MATCH 行淡显(不藏:那也是比对过的事实),非 MATCH 才是要看的 */ /* 差异明细:MATCH 行淡显(不藏:那也是比对过的事实),非 MATCH 才是要看的 */
tr.v-match td{opacity:.55} 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 历史行:首格挂一枚「历史」灰签 */ /* 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} 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{display:block;font-size:11px;color:var(--ink-3);font-weight:400}
@@ -305,8 +268,8 @@ export const LEDGER_BOARD = `<!doctype html>
<header class="mast"> <header class="mast">
<div class="kick">SS02 需求分析与方案匹配 · 活动台账视图</div> <div class="kick">SS02 需求分析与方案匹配 · 活动台账视图</div>
<h1><span class="code">ACT-WORK-05</span>关键台账数据</h1> <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 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="substitution.relation.original_internal_pn"></b> → 候选 <b data-slot="substitution.relation.candidate_internal_pn"></b> · 芯片类型 <span data-slot="parts.chip_type.type_name" data-blank></span> · 规则版本 <b data-slot="substitution.relation.rule_version"></b></p>
<p class="sub verdict">评估结论:<span data-slot="${D}.verdict.summary" data-blank></span></p> <p class="sub verdict">评估结论:<span data-slot="${D}.case.summary" data-blank></span></p>
<div class="legend"> <div class="legend">
<span class="li"><span class="dot full"></span>正式台账直读 —— 放行落地后的事实,按案号圈出这一单的行</span> <span class="li"><span class="dot full"></span>正式台账直读 —— 放行落地后的事实,按案号圈出这一单的行</span>
<span class="li"><span class="dot half"></span>同料号的历史验证记录(别单 / 线下留下的,作本次评估的证据引用)</span> <span class="li"><span class="dot half"></span>同料号的历史验证记录(别单 / 线下留下的,作本次评估的证据引用)</span>
@@ -319,64 +282,57 @@ export const LEDGER_BOARD = `<!doctype html>
<!-- 重要性分层 --> <!-- 重要性分层 -->
<div class="layers"> <div class="layers">
<div class="layer"><b><span class="lg">P-08</span> 芯片主数据</b>核心输入 · 评估原材料,查无即硬阻塞</div> <div class="layer"><b><span class="lg">P-08</span> 芯片主数据</b>核心输入 · 评估原材料,查无即硬阻塞</div>
<div class="layer evid"><b><span class="lg">字段字典</span> 类型与核心参数</b>比对基准 · 按芯片类型定义要比的字段与硬软约束</div>
<div class="layer core-out"><b><span class="lg">P-11</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 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> </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 --> <!-- P-08 -->
<section id="p08"> <section id="p08">
<div class="sec-head"> <div class="sec-head">
<h2><span class="lid">P-08</span>芯片主数据台账</h2> <h2><span class="lid">P-08</span>芯片主数据台账</h2>
<span class="ltag in">核心输入 · R(查无 → 数据工单创建)</span> <span class="ltag in">核心输入 · R(查无 → 数据工单创建)</span>
<span class="why">原 PN 与候选 PN 的物料基准 + 标准化参数集(子表 P-08 参数),逐项比对的原材料</span> <span class="why">原 PN 与候选 PN 的物料基准 + 标准化参数集(子表 P-08 参数),逐项比对的原材料</span>
<span class="cnt">正式表 · <i data-slot="parts_profile_rows"></i> 条(本活动相关)</span> <span class="cnt">正式表 · <i data-slot="parts_profile_rows"></i> 条(本次评估相关)</span>
</div> </div>
<div class="tbl-wrap"> <div class="tbl-wrap">
<table> <table>
${th('内部料号', '品牌', '规格描述', '封装', '生命周期', 'Datasheet 版本', '标准化参数集', '适配场景')} ${th('内部料号', '品牌', '规格描述', '生命周期', 'Datasheet 版本', '标准化参数集')}
<tbody data-rows="parts.profile"> <tbody data-rows="parts.profile">
<tr> <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 class="pn mono"><span data-slot="internal_pn"></span><span class="pnrow" data-rows="${D}.part" 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="brand_name" data-blank></td>
<td data-slot="specification_description" 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><span class="st lc" data-class-of="lifecycle_status"></span></td>
<td class="mono" data-slot="datasheet_version" data-blank></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><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> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<p class="aband">本轮放弃的候选(未建档、未评估):<span data-rows="${D}.pn_check"><i data-class-of="exists_status" data-slot="internal_pn"></i></span></p> </section>
<!-- 字段字典 -->
<section id="dict">
<div class="sec-head">
<h2><span class="lid">字段字典</span>芯片类型与核心参数</h2>
<span class="ltag evid">比对基准 · R</span>
<span class="why">两颗料共同归属的芯片类型,及该类型定义的核心参数字段(名字 / 说明 / 硬软约束 / 单位)</span>
<span class="cnt">类型 · <b data-slot="parts.chip_type.type_name" data-blank></b> · 字段 <i data-slot="parts_type_param_rows"></i> 条</span>
</div>
<div class="tbl-wrap">
<table>
${th('参数名', '参数说明', '约束', '参考单位')}
<tbody data-rows="parts.type_param">
<tr>
<td class="cust" data-slot="param_name"></td>
<td class="sub" data-slot="param_desc" data-blank></td>
<td><span class="enumtxt con" data-class-of="constraint_type"></span></td>
<td class="mono" data-slot="param_unit" data-blank></td>
</tr>
</tbody>
</table>
</div>
</section> </section>
<!-- P-11 --> <!-- P-11 -->
@@ -384,71 +340,44 @@ export const LEDGER_BOARD = `<!doctype html>
<div class="sec-head"> <div class="sec-head">
<h2><span class="lid">P-11</span>替代选型与批准关系库 · 替代关系主记录</h2> <h2><span class="lid">P-11</span>替代选型与批准关系库 · 替代关系主记录</h2>
<span class="ltag out">核心产出 · R+W</span> <span class="ltag out">核心产出 · R+W</span>
<span class="why">每个候选一条关系记录;客户批准状态为只读边界,本活动不写</span> <span class="why">原 PN → 候选 PN 一条关系记录;推荐等级 / 风险 / 摘要由规则算出,客户批准状态为只读边界,本活动不写</span>
<span class="cnt">正式表 · <i data-slot="substitution_relation_rows"></i> 条</span> <span class="cnt">正式表 · <i data-slot="substitution_relation_rows"></i> 条</span>
</div> </div>
<div class="tbl-wrap" style="margin-bottom:14px"> <div class="tbl-wrap" style="margin-bottom:14px">
<table> <table>
${th('替代关系 ID', '原 → 候选', '替代类型', '推荐等级', '分析状态', '风险', '差异摘要', '验证要求 / 回链', '规则版本', '客户批准(只读)')} ${th('替代关系 ID', '原 → 候选', '推荐等级', '分析状态', '风险', '差异摘要 / 理由', '验证要求 / 回链', '规则版本')}
<tbody data-rows="substitution.relation"> <tbody data-rows="substitution.relation">
<tr> <tr>
<td class="mono" data-slot="substitution_relation_id"></td> <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 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 rec" data-class-of="recommendation_level"></span></td>
<td><span class="st an" data-class-of="analysis_status"></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 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><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"><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 class="mono" data-slot="rule_version" data-blank></td>
<td><span class="st ap" data-class-of="customer_approval_status"></span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="sec-head" style="margin-bottom:10px"> <div class="sec-head" style="margin-bottom:10px">
<h2 style="font-size:13.5px"><span class="lid">P-11</span>差异明细子表(由双方 P-08 标准化参数集逐项比对得出;MATCH 项淡显)</h2> <h2 style="font-size:13.5px"><span class="lid">P-11</span>差异明细子表(按字段字典逐项比对得出;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> <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>
<div class="tbl-wrap"> <div class="tbl-wrap">
<table> <table>
${th('所属关系', '参数项', '原值 → 候选值', '约束', '结论', '风险', '证据引用', '影响说明')} ${th('参数项', '约束', '原值 → 候选值', '单位', '结论', '风险', '判断依据', '影响说明')}
<tbody data-rows="substitution.difference_item"> <tbody data-rows="substitution.difference_item">
<tr data-class-of="difference_status"> <tr data-class-of="difference_status">
<td class="mono" data-slot="substitution_relation_id"></td>
<td data-slot="parameter_name"></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="enumtxt con" data-class-of="constraint_type"></span></td>
<td class="mono"><span data-slot="original_value" data-blank></span> → <span data-slot="candidate_value" data-blank></span></td>
<td data-slot="parameter_unit" data-blank></td>
<td><span class="st dst" data-class-of="difference_status"></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><span class="st rk" data-class-of="risk_level"></span></td>
<td class="mono" data-slot="evidence_ref" data-blank></td> <td class="mono" data-slot="evidence_note" data-blank></td>
<td><span data-slot="impact_statement" data-blank></span><span class="sub" data-slot="unknown_disposition" data-blank></span></td> <td data-slot="impact_statement" data-blank></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> </tr>
</tbody> </tbody>
</table> </table>
@@ -460,33 +389,29 @@ export const LEDGER_BOARD = `<!doctype html>
<div class="sec-head"> <div class="sec-head">
<h2><span class="lid">P-06</span>样品交付与芯片验证记录台账</h2> <h2><span class="lid">P-06</span>样品交付与芯片验证记录台账</h2>
<span class="ltag loop">闭环去向 · R + 条件写(不可创建,只能实测产生)</span> <span class="ltag loop">闭环去向 · R + 条件写(不可创建,只能实测产生)</span>
<span class="why">UNKNOWN / CONDITIONAL 的收口处;验证完成后回链 P-11</span> <span class="why">UNKNOWN / CONDITIONAL 的收口处;一单固定一条</span>
<span class="cnt">本单挂的 · <i data-slot="validation_record_rows"></i> 条 · 同料号历史 · <i data-slot="validation_history_rows"></i> 条</span> <span class="cnt">本单挂的 · <i data-slot="validation_record_rows"></i> 条 · 同料号历史 · <i data-slot="validation_history_rows"></i> 条</span>
</div> </div>
<div class="tbl-wrap"> <div class="tbl-wrap">
<table> <table>
${th('验证 ID', '关联需求', '验证料号', '验证类型', '测试条件', '结果 / 状态', '问题与风险', '后续动作')} ${th('验证 ID', '验证料号', '验证类型', '测试条件', '结果 / 状态', '后续动作')}
<tbody data-rows="validation.record"> <tbody data-rows="validation.record">
<tr> <tr>
<td class="mono" data-slot="validation_id"></td> <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 class="mono" data-slot="validation_internal_pn"></td>
<td><span class="enumtxt vt" data-class-of="validation_type"></span></td> <td><span class="enumtxt vt" data-class-of="validation_type"></span></td>
<td data-slot="test_condition" data-blank></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><span class="st vs" data-class-of="validation_status"></span></td>
<td data-slot="issue_risk" data-blank></td>
<td data-slot="next_validation_action" data-blank></td> <td data-slot="next_validation_action" data-blank></td>
</tr> </tr>
</tbody> </tbody>
<tbody class="hist" data-rows="validation.history"> <tbody class="hist" data-rows="validation.history">
<tr> <tr>
<td class="mono" data-slot="validation_id"></td> <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 class="mono" data-slot="validation_internal_pn"></td>
<td><span class="enumtxt vt" data-class-of="validation_type"></span></td> <td><span class="enumtxt vt" data-class-of="validation_type"></span></td>
<td data-slot="test_condition" data-blank></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><span class="st vs" data-class-of="validation_status"></span></td>
<td data-slot="issue_risk" data-blank></td>
<td data-slot="next_validation_action" data-blank></td> <td data-slot="next_validation_action" data-blank></td>
</tr> </tr>
</tbody> </tbody>
@@ -500,15 +425,13 @@ export const LEDGER_BOARD = `<!doctype html>
<h2>次要台账(只读引用,不进主看板)</h2> <h2>次要台账(只读引用,不进主看板)</h2>
</div> </div>
<div class="minor"> <div class="minor">
<div class="m"><b><span class="lid">P-09</span>客户技术与机会档案</b>提供 BOM 位与客户技术栈背景;缺口由执行工单补齐。</div> <div class="m"><b><span class="lid">GOV</span>评估规则库</b>四态判定与推荐等级口径;本次锁定版本 <span class="mono" data-slot="substitution.relation.rule_version"></span>,随结论一并落账。</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> </div>
</section> </section>
<footer> <footer>
<span>SS02 · ACT-WORK-05 差异评估 · 关键台账数据视图</span> <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> <span style="font-family:var(--mono)">数据来源:正式台账(parts / substitution / validation / sub_eval)· 实例 <span data-slot="case:instanceNo"></span> · 案号 <span data-slot="case:ref"></span></span>
</footer> </footer>
</div> </div>
@@ -14,7 +14,8 @@
* 的 step④),够不够齐、该不该出现由样式表判(§5「模版不许算」)。 * 的 step④),够不够齐、该不该出现由样式表判(§5「模版不许算」)。
* *
* `difference-review`(件② 差异评估结论审核)已由 Task 8 重做并交付(见文件尾「局面:差异评估 * `difference-review`(件② 差异评估结论审核)已由 Task 8 重做并交付(见文件尾「局面:差异评估
* 结论审核」一节);`landing-confirm`(件③ 落账确认)仍待 Task 9 重做(同名新建)。 * 结论审核」一节);`landing-confirm`(件③ 落账确认)已由 Task 9 重做并交付(见文件尾
* 「局面:落账确认」一节)—— 三张件此刻齐了。
* *
* 版面语言仍是「搬,不要照着做」(《HTML报告生成规范》§7):`P08_CSS`(逐字节抽自 * 版面语言仍是「搬,不要照着做」(《HTML报告生成规范》§7):`P08_CSS`(逐字节抽自
* `docs/SS02-ACT-WORK-05-html/ACT-WORK-05-数据工单-01-芯片主数据创建.html`, * `docs/SS02-ACT-WORK-05-html/ACT-WORK-05-数据工单-01-芯片主数据创建.html`,
@@ -25,7 +26,7 @@
* `data-class-of` 的 `v-*` 别名):目标件是写死数据的静态 demo,没有「N 行草稿」 * `data-class-of` 的 `v-*` 别名):目标件是写死数据的静态 demo,没有「N 行草稿」
* 「编辑态与归档态二选一」这些平台概念。 * 「编辑态与归档态二选一」这些平台概念。
*/ */
import { P08_CSS, ACC1_CSS } from './target-style.ts'; import { P08_CSS, ACC1_CSS, ACC2_CSS } from './target-style.ts';
import { D } from '../constants.ts'; import { D } from '../constants.ts';
/* ==================================================================================== /* ====================================================================================
@@ -647,3 +648,148 @@ export const DIFFERENCE_REVIEW = doc({
CLOSING_SUMMARY + CLOSING_SUMMARY +
flow('<b>审批:</b>本工单只承载内容;逐条核对无误后,请在对话区的【验收卡】中确认通过。通过后进入落账放行(T_release),签字后正式落账并发口。'), flow('<b>审批:</b>本工单只承载内容;逐条核对无误后,请在对话区的【验收卡】中确认通过。通过后进入落账放行(T_release),签字后正式落账并发口。'),
}); });
/* ==================== 局面:落账确认(landing-confirm,T_release 的终局验收工单) ==================== */
/*
* **全只读,答「这一签下去发生什么」(spec §6.3)**:进哪几张表各几行(摆人读名)· 结论卡
* (等级 + 一句话)· 四态分布并把硬约束不符的逐条点名 · 待验项(P-06 的测试条件与后续动作)·
* 发哪一口。这不是签字前要核的判据表(那张表已经在 S3 核过一遍;T_release 自己的
* `acceptanceChecklist` 由验收卡渲染,不在这份 HTML 里重复一遍)—— 这里摆的是**事实**:
* 签下去会落地成什么样子,签字的人拿它核对「摆出来的这些是不是要落的」。
*
* 主题色沿用 `ACC2_CSS`(目标件「验收工单-02」抽出来的样式表,已改成验收绿 --ledger:#0aa294,
* 见 target-style.ts 头注)——但 `ACC2_CSS` 只有基座壳与 `.st`/`.risk` 两族徽标,**没有**
* `.rec`(推荐等级)那族大徽章的基座形状与 `.tbl-wrap` 的横滑修正,这份文件独立声明
* `BIND_LANDING`(与 `BIND_ACC1_EDIT`/`BIND_ACC1_BADGE`/`BIND_ACC1_LAYOUT` 互不共享 ——
* 两份 HTML 各有各的 `<style>`,不存在跨文档级联)。
*/
const BIND_LANDING = `
/* 表宽超出要能横着滑(陷阱表规矩)—— ACC2_CSS 的 .tbl-wrap 写的是 overflow:hidden */
.tbl-wrap { overflow-x: auto; overflow-y: hidden; }
/* 结论卡:推荐等级大徽章 + 一句话摘要 */
.verdict-card{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin:0 0 18px;padding:14px 16px;border:1px solid var(--ledger-line);border-radius:12px;background:var(--ledger-soft)}
.verdict-sum{flex:1 1 260px;min-width:0;font-size:13px;color:var(--ink-2);line-height:1.6;overflow-wrap:anywhere}
/* 推荐等级徽标 —— ACC2_CSS 没有 .rec 基座形状(那是 ACC1_CSS 才有的),这里补齐,与差异评估结论审核件同一套值域 */
.rec{display:inline-block;font-size:11px;font-weight:800;padding:2px 12px;border-radius:999px;border:1px solid transparent;white-space:nowrap}
${badgeCss('.rec', [
['recommended', '推荐', OK],
['conditional', '有条件推荐', AMBER],
['not_recommended', '不推荐', DANGER],
])}
.rec.rec-lg{font-size:15px;padding:6px 20px;letter-spacing:.02em}
/* 约束类型徽标(hard/soft)—— 与 input-supplement/difference-review 同一套颜色语义,独立声明(不跨文档共享) */
.ct{display:inline-block;font-size:11px;font-weight:700;padding:1px 8px;border-radius:999px;border:1px solid transparent;white-space:nowrap}
${badgeCss('.ct', [
['hard', '硬约束', DANGER],
['soft', '软约束', MUTED],
])}
/* 四态计数 —— 签字页只摆数,不摆宽度分布条 */
.four{display:flex;flex-wrap:wrap;gap:16px;margin:0 0 18px}
.four-item{font-size:12.5px;color:var(--ink-3);display:inline-flex;align-items:center;gap:5px}
.four-item b{font-weight:800;color:var(--ink);font-size:13.5px;font-family:var(--mono)}
.four-item.m b{color:var(--ok)}
.four-item.x b{color:var(--danger)}
.four-item.u b{color:#8f6a12}
.four-item.c b{color:var(--brand-deep)}
/* 硬约束不符逐条点名 —— 整表渲出来,CSS 只留 judgement_class = hard_mismatch 的那几行(判断留在样式表,模版不判) */
.hardbox{margin:0 0 18px}
.hardbox-empty{display:none;margin:0;padding:10px 14px;border-radius:9px;background:var(--ok-soft);border:1px solid #bfe6b4;color:var(--ok);font-size:12.5px}
.hardbox:not(:has(tr.v-${vtok('hard_mismatch')})) .hardbox-empty{display:block}
.hardbox:not(:has(tr.v-${vtok('hard_mismatch')})) .tbl-wrap{display:none}
.hardbox table tbody tr:not(.v-${vtok('hard_mismatch')}){display:none}
/* 待验项 —— kv 行,与差异评估结论审核件「收口结论」同一套版式,独立声明 */
.kv{display:flex;gap:10px;padding:9px 0;border-bottom:1px solid var(--line-soft);font-size:12.5px}
.kv:last-child{border-bottom:0}
.kv i{flex:0 0 84px;font-style:normal;color:var(--ink-4);font-size:11px;padding-top:1px}
.kv span{flex:1 1 auto;color:var(--ink-2);line-height:1.6;overflow-wrap:anywhere}
.kv span:empty::after{content:"—";color:var(--ink-4)}
`;
/**
* 落账去向 —— 摆「进哪几张表各几行」,人读名 + 基座读数(spec §6.3 第一件事)。
* 三行是**固定的**(必交付物 `landing`/`difference-items` 承载的正是这三张表,见 manifest.ts
* `contract.output.requiredDeliverables`),不是 `data-rows` 驱动的动态表 —— 行数本身才是
* 要摆的数据,行的「有没有」不需要模版去猜。
*/
const LANDING_TABLE = `<div class="tbl-wrap"><table>
<thead><tr><th style="width:260px" data-h="落账表">落账表</th><th data-h="行数">行数</th></tr></thead>
<tbody>
<tr><td class="cust">替代关系主记录(P-11)</td><td class="mono"><span data-slot="substitution_relation_rows"></span> 行</td></tr>
<tr><td class="cust">差异明细(P-11 子表)</td><td class="mono"><span data-slot="substitution_difference_item_rows"></span> 行</td></tr>
<tr><td class="cust">验证记录(P-06)</td><td class="mono"><span data-slot="validation_record_rows"></span> 行</td></tr>
</tbody>
</table></div>`;
/** 结论卡 —— 推荐等级大徽章 + 一句话摘要(`sub_eval.case.summary`,T_release 自己那笔投影拼的整句)。 */
const VERDICT_CARD = `<div class="verdict-card">
<span class="rec rec-lg" data-class-of="recommendation_level"></span>
<span class="verdict-sum" data-slot="summary" data-blank></span>
</div>`;
/** 四态计数 —— 与差异评估结论审核件同一批基座读数,这里不画宽度条,只摆数。 */
const FOUR_STATE = `<div class="four">
<span class="four-item m">MATCH · <b data-slot="${DIST_PREFIX}match"></b></span>
<span class="four-item x">MISMATCH · <b data-slot="${DIST_PREFIX}mismatch"></b></span>
<span class="four-item u">UNKNOWN · <b data-slot="${DIST_PREFIX}unknown"></b></span>
<span class="four-item c">CONDITIONAL · <b data-slot="${DIST_PREFIX}conditional"></b></span>
</div>`;
/**
* 硬约束不符逐条点名 —— **签字的人最该看见的就是它**。整表渲出来(`mine:substitution.difference_item`),
* 行上挂 `data-class-of="judgement_class"`,CSS 只留 `judgement_class = hard_mismatch` 的那几行
* (见 `BIND_LANDING` 里 `.hardbox table tbody tr:not(.v-${vtok('hard_mismatch')})`)——
* **`vtok('hard_mismatch')` 不是 `hard_mismatch` 本身**:`classToken` 只留 `[a-z0-9-]`、
* 吃掉下划线,选择器一律从值算出来,不手写(本包已在 2026-08-27 栽过一次同款事故)。
* 一条都没有时摆一句正面的话(`.hardbox-empty`),不是留一张空表让人猜「是没有还是没算」。
*/
const HARD_MISMATCH_TABLE = `<div class="hardbox">
<p class="hardbox-empty">没有硬约束不符项。</p>
<div class="tbl-wrap"><table>
<thead><tr><th data-h="参数项">参数项</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></tr></thead>
<tbody data-rows="mine:substitution.difference_item">
<tr data-class-of="judgement_class">
<td class="cust" data-slot="parameter_name"></td>
<td><span class="ct" data-class-of="constraint_type"></span></td>
<td class="mono" data-slot="original_value" data-blank></td>
<td class="mono" data-slot="candidate_value" data-blank></td>
<td data-slot="evidence_note" data-blank></td>
<td data-slot="impact_statement" data-blank></td>
</tr>
</tbody>
</table></div>
</div>`;
/** 待验项 —— P-06 的测试条件与后续动作(一单固定一条,直接 kv 行摆,不必 data-rows)。 */
const VALIDATION_BLOCK = `<div class="kv-box">
<div class="kv"><i>测试条件</i><span data-slot="test_condition" data-blank></span></div>
<div class="kv"><i>后续动作</i><span data-slot="next_validation_action" data-blank></span></div>
</div>`;
export const LANDING_CONFIRM = doc({
css: ACC2_CSS,
alias: BIND_LANDING,
badge: '验收',
tag: '验收工单',
kicker: 'SS02 · ACT-WORK-05 差异评估 · 验收工单 2/2',
title: '评审放行与落账确认',
step: '步骤 4/4 · 提交 ACT-GATE-03',
footTag: '工单类型:验收',
body:
zone('这一签下去发生什么', '全只读 —— 签之前只核对,要改回上一步的验收工单重做', ZIC_TASK) +
note([
'<span class="need">这一步做什么</span>:核对下面即将落账的内容,确认无误后在对话区的【验收卡】里签字放行 —— 签字后正式落账,不能撤回。',
'<span class="done">系统已完成</span>:推荐等级 / 差异摘要 / 风险按规则从下面的差异明细算出,不是这一步现拟 —— 签字确认的是「摆出来的这些是不是要落的」,不是重新判断。',
'<span class="need">不写客户批准</span>:客户批准状态由独立审批流维护,本活动只答技术可替代性,不产生那一列。',
]) +
zone('落账去向', '进哪几张表、各几行 —— 表由必交付物声明承载,行数即将落定', ZIC_TABLE) +
LANDING_TABLE +
zone('结论', '推荐等级 + 一句话摘要', ZIC_TASK) +
VERDICT_CARD +
zone('四态分布', '逐条差异明细的结论分布;硬约束不符的逐条点名 —— 签字前最该看的就是它', ZIC_TABLE) +
FOUR_STATE +
HARD_MISMATCH_TABLE +
zone('待验项', 'P-06 的测试条件与后续动作 —— 有 UNKNOWN / CONDITIONAL 才有内容,全推荐则写「本轮无待验项」', ZIC_TASK) +
VALIDATION_BLOCK +
flow('<b>签字与发射:</b>本工单只承载内容;确认请在对话区的【验收卡】中签字放行。签字后即落账,发射 <b>assessment-released</b>(评估结论已落账)—— 载荷:评估案号 / 替代关系ID / 原内部料号 / 候选内部料号 / 推荐等级 / MATCH·MISMATCH·UNKNOWN·CONDITIONAL 条数 / 关联验证ID。'),
});