feat(w05): 差异评估结论审核的收口四格可就地改 —— 判定归规则,话归人

用户:「差异评估结论审核这个界面的收口结论下面这 4 个字段也需要可以编辑」

这四格(推荐理由 / 差异摘要 / 替代风险 / 验证要求)原本是**故意**只读的,理由写在源码里:
「让人直接改这几格,等于让他给自己没做的判断盖章」。用户改了这个口径 —— 审核人对结论
有最终发言权。原理由留在注释里备查。

**光把格子打开等于没开**:`RELATION_PROJECTION` 每跑一次就按规则把这四列重写一遍,
而人在件上按保存会触发 quiet 拍、quiet 恰恰跑 project —— 人刚写下的话当场被规则值冲掉,
那是「让你改、改完被冲掉」,比不给改更糟(基座不记「谁写的」,配方分不清一格是人改的
还是自己写的,所以只能从配方这一侧下手)。

改法:S3 换用新的 `RELATION_RECOMPUTE` —— 与 `DIFFERENCE_ITEM_RECOMPUTE` 同一手法,
**只列主键 + recommendation_level**。patch 是稀疏的、DB 侧是 jsonb 浅合并,没列的列原样
留着,人改过的四格因此保得住。**推荐等级仍然重算且不容手改**:它是规则按四态分布算出来
的判据核心(件② 判据表 / 件③ 落账结论 / 出口谓词都读它),「人改四态 → 等级跟着变」
这条链不能断。分工因此是:**判定归规则,话归人**。

代价说在明处,件上那段说明已改口:改完四态,这四格不会自动跟着改口(否则刚写的话就没了)
——「四态改完,记得回头看看这四格还对不对」。界面不撒谎优先于自动。

件上:三句话走 cell(只读份 + 孪生输入),风险只读态摆徽标、编辑态换 select(枚举列不许
直显 low/medium/high),整块一个宿主一道闸,主键钥匙藏在 .rowkey 里但真挂 data-col。
.summary-box 不是表格 —— setOpen 对非表格容器把 .on 落在 data-edit 元素自身,CSS 按
.summary-box.on 写。

测:件上四格可改 + 一个宿主一道闸 + 钥匙在场;S3 重算笔四列一个都不许出现、判定仍在且
与 S2 共用同一条 when 链。四条变异各红一条(S3 退回用 RELATION_PROJECTION / 混进
difference_summary / 混进 risk_level / 判定不再重算)。pnpm gates 退出码 0(2495 测)。
包版本 2.3.0 -> 2.4.0。

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-31 11:53:33 +08:00
co-authored by Claude Opus 5
parent e5ca9caaa6
commit 1e3e5d40c6
5 changed files with 139 additions and 28 deletions
@@ -451,7 +451,8 @@ describe('报告模版:编辑面与只读面各就各位', () => {
it('每个可编辑格子的 data-col 都在对应台账对象的声明里(input-supplement 一屏四张表,四张台账对象的声明并起来看;difference-review 一张表)', () => {
const cases: Array<[string, Array<[string, string]>]> = [
['input-supplement', [['parts', 'profile'], ['parts', 'chip_type'], ['parts', 'type_param'], ['parts', 'parameter']]],
['difference-review', [['substitution', 'difference_item']]],
/* 件② 2026-08-31 起多一张:收口四格可就地改,写向 substitution.relation */
['difference-review', [['substitution', 'difference_item'], ['substitution', 'relation']]],
];
for (const [view, objects] of cases) {
const cols = new Set([...viewHtml(view).matchAll(/data-col="([^"]+)"/g)].map((m) => m[1] ?? ''));
@@ -744,12 +745,30 @@ describe('件② 差异评估结论审核', () => {
expect(html).toContain('data-col="difference_item_id"');
});
it('**推荐理由 / 摘要 / 风险 / 验证要求只读** —— 让人直接改摘要,等于让他给自己没做的判断盖章', () => {
/**
* **收口四格可就地改**(2026-08-31 用户:「差异评估结论审核这个界面的收口结论下面这 4 个
* 字段也需要可以编辑」)。此前是只读的,理由是「让人直接改,等于给自己没做的判断盖章」——
* 用户改了这个口径:审核人对结论有最终发言权。
*
* **配方那一侧必须一起改,否则等于没开**:`RELATION_PROJECTION` 每跑一次就把这四列按规则
* 重写一遍,而人在件上按保存会触发 quiet 拍、quiet 恰恰跑 project —— 人改完一保存就被冲掉。
* S3 因此换成只重算判定的 `RELATION_RECOMPUTE`(那一组断言在「S3 只重算判定」里)。
*/
it('**收口四格可就地改**:三句话走 cell(只读份 + 孪生输入)、风险走徽标 + select,整块一个宿主一道闸', () => {
for (const col of ['recommendation_reason', 'difference_summary', 'verification_requirement']) {
expect(html).toContain(`data-slot="${col}"`);
expect(html).not.toContain(`data-col="${col}"`);
expect(html, col).toContain(`data-slot="${col}"`);
expect(html, col).toContain(`data-col="${col}"`);
}
expect(html).not.toContain('data-edit="substitution.relation"');
/* 风险是枚举:只读态摆徽标(不裸显 low/medium/high),编辑态换 select */
expect(html).toContain('class="risk-badge" data-class-of="risk_level"');
expect(html).toMatch(/<select class="jc-cell" data-col="risk_level" required>/);
/* 一个地址一个宿主(bodyOf 是单 querySelector),且必须配一道闸 —— 闸不在就打不开(Task 7 那颗按不下去的钮) */
expect((html.match(/data-edit="substitution\.relation"/g) ?? []).length).toBe(1);
expect(html).toContain('data-edit-for="substitution.relation"');
expect(html).toContain('data-edit-open="substitution.relation"');
/* 主键钥匙在场且藏起来 —— 漏了钥匙这一行凑不出主键(2026-08-02 栽过) */
expect(html).toContain('data-col="substitution_relation_id"');
expect(html).toContain('.summary-box .rowkey{display:none}');
});
it('等级与四态用 data-class-of 上色 —— 判断留在样式表,模版不判', () => {
@@ -771,7 +790,9 @@ describe('件② 差异评估结论审核', () => {
for (const col of [
'parameter_name', 'parameter_desc', 'constraint_type',
'original_value', 'candidate_value', 'parameter_unit',
'recommendation_level', 'risk_level',
/* 推荐等级**仍然只读**:它是规则按四态分布算出来的判据核心,人改四态即改它,不容手改。
风险(risk_level)2026-08-31 起交给人,已从这份名单里移出 —— 见「收口四格可就地改」。 */
'recommendation_level',
]) {
expect(html, col).not.toContain(`data-col="${col}"`);
}