Archived
fix(w05): S3 补一笔只重算派生列 —— 人改了四态,结论才跟得上
浏览器走查实测:件上承诺「你改动下表里任何一条四态,这五项当场重算」, 而把使能阈值由 mismatch 改成 match 保存后,同一行的 judgement_class 仍是 hard_mismatch、risk_level 仍 high —— 行内自相矛盾,结论纹丝不动 (仍「不推荐 / 高风险 / 1 项硬约束不符」)。 断链在:S3 复用的 RELATION_PROJECTION 算结论读的是 judgement_class 的 tally, 而 judgement_class 由 difference_status 在差异明细那一笔的 when 链推出来 —— 那一笔只在 S2。人改的 difference_status 没有任何东西把它转成 judgement_class。 当初不把那一笔放进 S3 是有真理由的:它的 difference_status 走 lookup:'result:items', 而 result: 只在 analyze 那一拍存在,在 S3 重跑会把人刚改的值抹成空。 修法:加 DIFFERENCE_ITEM_RECOMPUTE 作 S3 的第一笔 —— each(mine:…) 不 prune (避开「已落地行不许 prune」),fields 只有两个主键列 + JUDGEMENT_DERIVED_FIELDS (judgement_class / risk_level),**一列都不多写**,稀疏 patch 绝不碰 difference_status 与人可改的叙述列;排在 RELATION_PROJECTION 之前。 when 链提成 JUDGEMENT_DERIVED_FIELDS 一处真源,S2 与 S3 spread 同一个, 新测试用 toBe 断言引用相等(不是形状相同)。 **旧测试为什么漏掉**:w05-s3-review-recompute.test.ts 的 HUMAN_EDIT mock 在同一个 patch 里同时写了 difference_status 和 judgement_class —— **把系统本该做的翻译自己 先算好了**,那座缺失的桥就此被掩盖。现改为只写人真能碰的列(difference_status + evidence_note),并加修前/修后成对断言。 变异验证两路都真红:①拆掉配方里那一笔 → 2/3 红,复原 → 3/3 绿; ②git stash 真 case-model.ts → steps.test.ts 5 条结构测试红,pop → 38/38 绿。 包版本 2.1.8(已装,SQL 核过)。pnpm gates 退出码 0(2454 测)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -140,6 +140,70 @@ const SHARED_LET = {
|
||||
relationId: { cat: ['SUB-', { lookup: 'mine:sub_eval.case', by: {}, take: 'eval_ref', else: '' }] },
|
||||
} as const;
|
||||
|
||||
/*
|
||||
* **人改了四态,判定分类 / 风险等级要跟着重算 —— 而这两列的算法此前只在 S2 出现过一次**
|
||||
* (2026-08-30,治「结论纹丝不动」的要害缺陷:件上白纸黑字承诺「你改动下表里任何一条
|
||||
* 四态,这五项当场重算」——实测是假的。人在审核件上把某一行的 `difference_status` 从
|
||||
* `mismatch` 改成 `match` 并保存,草稿里那一行变成 `difference_status=match` 但
|
||||
* `judgement_class` 仍是旧的 `hard_mismatch`(**同一行自相矛盾**);`RELATION_PROJECTION`/
|
||||
* `VALIDATION_PROJECTION` 读的是 `judgement_class` 的 tally,不是 `difference_status`
|
||||
* 本身,两者之间从来没有一座桥。**S3 存在的全部意义就是让人纠正 AI;纠正流不下去,
|
||||
* S3 就是摆设。**
|
||||
*
|
||||
* **这段 when 链与 S2 差异明细那一笔共用同一份**(下面 `substitution.difference_item`
|
||||
* 那笔 `project` 的 `fields` 里 `...JUDGEMENT_DERIVED_FIELDS`)—— 判定规则只许有一处真源,
|
||||
* 与 `RELATION_PROJECTION`/`VALIDATION_PROJECTION` 本身「S2 与 S3 共用同一份」同一条道理。
|
||||
*/
|
||||
const JUDGEMENT_DERIVED_FIELDS = {
|
||||
judgement_class: {
|
||||
when: 'difference_status = "match"', then: 'match',
|
||||
else: {
|
||||
when: 'difference_status = "unknown"', then: 'unknown',
|
||||
else: {
|
||||
when: 'difference_status = "conditional"', then: 'conditional',
|
||||
else: { when: 'constraint_type = "hard"', then: 'hard_mismatch', else: 'soft_mismatch' },
|
||||
},
|
||||
},
|
||||
},
|
||||
risk_level: {
|
||||
when: 'judgement_class = "hard_mismatch"', then: 'high',
|
||||
else: { when: 'judgement_class = "match"', then: 'low', else: 'medium' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
/*
|
||||
* **S3 专用的重算笔**——逐行把 `judgement_class`/`risk_level` 按这一行**当下**的
|
||||
* `difference_status`(人可能刚改过)与 `constraint_type`(字典定的,人不改)重新算一遍。
|
||||
*
|
||||
* **为什么不能像 S2 那样把差异明细整笔搬过来重投**:S2 那一笔的 `difference_status` 走
|
||||
* `lookup:'result:items'`(AI 出参,只在 `analyze` 那一拍的拍内作用域里活着);S3 这一拍
|
||||
* `result:items` 早就不在了,`{lookup:...}` 落到 `else:''`,原笔重投会把人刚改的值**抹成空**
|
||||
* ——这是真实的设计张力,不是疏忽(评审已确认,见 `SHARED_LET` 定义处的头注)。
|
||||
*
|
||||
* **只列这两列,一个都不多写**:`fields` 之外没列的列,`draftWrite` 的 `patch` 是稀疏的
|
||||
* (只含显式给出的列;DB 侧 `patch = patch || excluded.patch` 是 jsonb **浅合并**,没给的列
|
||||
* 原样留着,`ledger-store.ts` 的 `put`)——于是这一笔天然不会碰 `difference_status` 自己,
|
||||
* 也不会碰人可以改的那几列(影响说明 / 判断依据 / UNKNOWN 去向)。**不带 `prune`**:
|
||||
* `from` 是对这张表自己的无过滤 `each`,枚举出来的集合本来就是「这张表现在有的全部行」,
|
||||
* `prune` 没有事情可做,而已落地的行本就不许 `prune`(H43)。
|
||||
*
|
||||
* **必须排在 `RELATION_PROJECTION`/`VALIDATION_PROJECTION` 之前**:后两笔读的
|
||||
* `substitution_difference_item_judgement_class_*` 是基座对这张表当下内容现算的 tally,
|
||||
* 「一笔读的是另一笔写出来的数,它就得排在那一笔后面」(与 `sub_eval.case` 那笔 `gate_reason`
|
||||
* 排在 ④⑤ 之后是同一条道理,见 S1 那笔 ⑥ 头上的大注释)。
|
||||
*/
|
||||
const DIFFERENCE_ITEM_RECOMPUTE = {
|
||||
kind: 'project' as const,
|
||||
into: 'substitution.difference_item',
|
||||
from: { each: 'mine:substitution.difference_item' },
|
||||
fields: {
|
||||
/* 主键原样带回,认的是这一行自己 —— 不是新建,是同 PK 覆盖 */
|
||||
substitution_relation_id: { col: 'substitution_relation_id' },
|
||||
difference_item_id: { col: 'difference_item_id' },
|
||||
...JUDGEMENT_DERIVED_FIELDS,
|
||||
},
|
||||
} as const;
|
||||
|
||||
/*
|
||||
* **关系与验证记录的投影,S2 与 S3 共用同一份**。
|
||||
* S2 算出第一版;人在审核件上改了某一条四态之后,S3 用**同一份**重算 ——
|
||||
@@ -628,21 +692,8 @@ export const ASSESSMENT_STEPS = [
|
||||
impact_statement: { lookup: 'result:items', by: { param_name: { col: 'param_name' } }, take: 'impact_statement', else: '' },
|
||||
evidence_note: { lookup: 'result:items', by: { param_name: { col: 'param_name' } }, take: 'evidence_note', else: '' },
|
||||
unknown_disposition: { lookup: 'result:items', by: { param_name: { col: 'param_name' } }, take: 'unknown_disposition', else: '' },
|
||||
/* 合成列:先认四态,最后才按约束类型把 mismatch 分成硬 / 软 */
|
||||
judgement_class: {
|
||||
when: 'difference_status = "match"', then: 'match',
|
||||
else: {
|
||||
when: 'difference_status = "unknown"', then: 'unknown',
|
||||
else: {
|
||||
when: 'difference_status = "conditional"', then: 'conditional',
|
||||
else: { when: 'constraint_type = "hard"', then: 'hard_mismatch', else: 'soft_mismatch' },
|
||||
},
|
||||
},
|
||||
},
|
||||
risk_level: {
|
||||
when: 'judgement_class = "hard_mismatch"', then: 'high',
|
||||
else: { when: 'judgement_class = "match"', then: 'low', else: 'medium' },
|
||||
},
|
||||
/* 合成列:先认四态,最后才按约束类型把 mismatch 分成硬 / 软 —— 与 S3 重算那一笔共用同一份 when 链,不许各写一份 */
|
||||
...JUDGEMENT_DERIVED_FIELDS,
|
||||
rule_version: RULE_VERSION,
|
||||
},
|
||||
},
|
||||
@@ -689,7 +740,13 @@ export const ASSESSMENT_STEPS = [
|
||||
*/
|
||||
let: SHARED_LET,
|
||||
do: [
|
||||
/* 人改了四态,等级 / 风险 / 摘要 / 验证要求跟着重算 —— 摘要只读,改的是四态 */
|
||||
/*
|
||||
* **人改了四态,先把这一行自己的判定分类 / 风险等级重算,关系与验证记录的投影才有
|
||||
* 真数据可读**(2026-08-30 修「结论纹丝不动」的要害缺陷,详见 `DIFFERENCE_ITEM_RECOMPUTE`
|
||||
* 定义处的头注)。三笔次序不能乱:①→②③ 是「一笔读的是另一笔写出来的数,它就得排在
|
||||
* 那一笔后面」的又一例 —— 摘要只读,改的是四态。
|
||||
*/
|
||||
DIFFERENCE_ITEM_RECOMPUTE,
|
||||
RELATION_PROJECTION,
|
||||
VALIDATION_PROJECTION,
|
||||
{
|
||||
|
||||
@@ -54,7 +54,7 @@ export const manifest: ActivityManifest = {
|
||||
activityId: ACTIVITY_ID,
|
||||
name: 'ACT-WORK-05 · 差异评估',
|
||||
/* 重写不是改 —— 同版重装是 no-op(静默丢失,踩过) */
|
||||
packageVersion: '2.1.7',
|
||||
packageVersion: '2.1.8',
|
||||
category: '需求分析与方案匹配',
|
||||
sdkVersion: '0.1.0-m5',
|
||||
checksum: 'dev-checksum-part-substitution-assessment',
|
||||
|
||||
Reference in New Issue
Block a user