Archived
fix(w05): 调人预算提到 24;「一条都没点到」的打回不再整表重判
用户:「怎么直接最后落帐的步骤直接没了?」—— 那一单终态是 exhausted:结论审核连打回四次, 每打回一次配方就重发一张审核卡、每张 +1 humanCalls,第八次触顶,放行那一步再没机会出现。 屏上看着像「那一步没了」,其实是单子先被烧死了。 ① 预算 8 → 24(用户当场定「那你就把预算提高」):常路只要 3 次(补数据 / 审核 / 放行), 而审核每打回一次就多叫一次人。授权面与资源面同一个数;测里改成「两处一致 + 下限 12」, 不再钉死某个具体数字。**这是配置不是机制** —— 机制上的正解是「人自己打回引出的那张卡 不算打扰」(他不是被打扰,是他叫的),已记 debt。 ② 「一条都没点到 → 整表算被点名」当天上线、当天撤掉:他那句是「收口结论里面的推荐理由 改为非常不推荐」—— 说的根本不是某条参数,是收口那四格(本来就能在件上直接改)。 整表重判的下场:AI 拿这句话把 5 条参数的结论改成「不匹配」,每条依据都写着 「按审核人意见调整为不匹配:推荐理由改为非常不推荐」——一句与它们无关的话,改了五行。 现在点不到名就一条都不改,并照实说清怎么才办得成(点参数名 / 收口四格在件上改)。 重判与写回同批加「真点到了名」的前提,免得白烧一次模型。 验收:e2e w05 5/5 released、被拒 0,案例 0 四条断言全绿;2.23.0 装库核过(humanCalls=24)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -406,11 +406,11 @@ describe('S3 / S4 配方', () => {
|
||||
const s4 = stepOf('T_release')?.auto?.do ?? [];
|
||||
const s2 = stepOf('S2_assess')?.auto?.do ?? [];
|
||||
|
||||
it('S3 九笔:圈范围 → 兜底扩范围 → 复核重判 → 落明细 → 明细重算 → 关系 → 验证记录 → 交代 → 卡', () => {
|
||||
expect(s3.map((a) => a.kind)).toEqual(['project', 'project', 'analyze', 'project', 'project', 'project', 'project', 'say', 'card']);
|
||||
for (const at of [0, 1, 3, 4]) expect(s3[at]?.kind === 'project' && s3[at].into).toBe('substitution.difference_item');
|
||||
expect(s3[5]?.kind === 'project' && s3[5].into).toBe('substitution.relation');
|
||||
expect(s3[6]?.kind === 'project' && s3[6].into).toBe('validation.record');
|
||||
it('S3 九笔:圈范围 → 复核重判 → 落明细 → 明细重算 → 关系 → 验证记录 → 两句交代 → 卡', () => {
|
||||
expect(s3.map((a) => a.kind)).toEqual(['project', 'analyze', 'project', 'project', 'project', 'project', 'say', 'say', 'card']);
|
||||
for (const at of [0, 2, 3]) expect(s3[at]?.kind === 'project' && s3[at].into).toBe('substitution.difference_item');
|
||||
expect(s3[4]?.kind === 'project' && s3[4].into).toBe('substitution.relation');
|
||||
expect(s3[5]?.kind === 'project' && s3[5].into).toBe('validation.record');
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -427,9 +427,17 @@ describe('S3 / S4 配方', () => {
|
||||
const act = s3[at];
|
||||
return act !== undefined && 'when' in act ? act.when : undefined;
|
||||
};
|
||||
for (const at of [0, 1, 2, 3]) expect(String(whenOf(at) ?? ''), `第 ${at} 笔`).toContain('case_redo_note != ""');
|
||||
for (const at of [0, 1, 2]) expect(String(whenOf(at) ?? ''), `第 ${at} 笔`).toContain('case_redo_note != ""');
|
||||
/* 后面那几笔(重算 / 关系 / 验证)照旧每一轮都跑 —— 人就地改了四态也要跟着重算 */
|
||||
for (const at of [4, 5, 6]) expect(whenOf(at), `第 ${at} 笔`).toBeUndefined();
|
||||
for (const at of [3, 4, 5]) expect(whenOf(at), `第 ${at} 笔`).toBeUndefined();
|
||||
/*
|
||||
* **点不到名就一条都不改**(2026-09-01 当天上线当天撤的那一笔:整表重判)——
|
||||
* 重判与写回都多一个前提「真点到了名」,免得一句与参数无关的话(实测:「收口结论里面的
|
||||
* 推荐理由改为非常不推荐」)把 5 条参数的结论一起改掉,还白烧一次模型。
|
||||
*/
|
||||
for (const at of [1, 2]) {
|
||||
expect(String(whenOf(at) ?? ''), `第 ${at} 笔`).toContain('substitution_difference_item_redo_scope_named > 0');
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -438,19 +446,23 @@ describe('S3 / S4 配方', () => {
|
||||
* 那一行;它改了没人问过的那条,`evidence_note` 里还写着「维持原判」。
|
||||
* 现在:①按字面点名圈出 `named`;③入参**只有** `named` 的行;④写回也只写这几行。
|
||||
*/
|
||||
it('圈范围:点名的标 named,其余 kept;一条都没点到时整表算被点名', () => {
|
||||
it('圈范围:点名的标 named,其余 kept —— 一条都没点到时什么都不改,只把话说清楚', () => {
|
||||
const mark = s3[0];
|
||||
const widen = s3[1];
|
||||
const markFields: Readonly<Record<string, unknown>> = mark?.kind === 'project' ? mark.fields : {};
|
||||
expect(markFields['redo_scope']).toEqual({ when: 'contains(case_redo_note, parameter_name)', then: 'named', else: 'kept' });
|
||||
/* 兜底那一笔读的是上一笔刚写下的 tally 读数 —— 不重新算一遍谁被点到 */
|
||||
expect(widen?.when).toBe('case_redo_note != "" and substitution_difference_item_redo_scope_named = 0');
|
||||
expect(widen?.kind === 'project' ? widen.fields['redo_scope'] : undefined).toBe('named');
|
||||
/* 没点到名的那一轮:一句交代,告诉他怎么才办得成 —— 而不是猜「他指的是全部」 */
|
||||
const noHit = s3[7];
|
||||
expect(noHit?.kind === 'say' && noHit.when).toBe('case_redo_note != "" and substitution_difference_item_redo_scope_named = 0');
|
||||
const text = noHit?.kind === 'say' ? String(noHit.text) : '';
|
||||
expect(text).toContain('一条都没改');
|
||||
expect(text).toContain('参数名');
|
||||
/* 收口四格不归 AI 判 —— 那几格人在件上直接改(2026-08-31 立的口径) */
|
||||
expect(text).toContain('件上直接改');
|
||||
});
|
||||
|
||||
it('重判与写回都只认 named 的行 —— 没点名的行 AI 连见都见不到,自然改不着', () => {
|
||||
const redo = s3[2];
|
||||
const land = s3[3];
|
||||
const redo = s3[1];
|
||||
const land = s3[2];
|
||||
const named = { each: 'mine:substitution.difference_item', where: { redo_scope: 'named' } };
|
||||
expect(redo?.kind === 'analyze' ? redo.input.from : undefined).toEqual(named);
|
||||
expect(land?.kind === 'project' ? land.from : undefined).toEqual(named);
|
||||
@@ -493,7 +505,7 @@ describe('S3 / S4 配方', () => {
|
||||
* `s3[0]` 必须是**排在关系 / 验证记录投影之前**的差异明细重算笔。
|
||||
*/
|
||||
it('差异明细重算笔:只列主键与两个派生列,不碰 difference_status 自己、也不碰人能改的叙事列', () => {
|
||||
const recompute = s3[4];
|
||||
const recompute = s3[3];
|
||||
expect(recompute?.kind === 'project' && recompute.from).toEqual({ each: 'mine:substitution.difference_item' });
|
||||
/* `recompute` 经 `s3[0]` 的 tuple 定位精确 narrow 到这一笔自己的字面量类型,它压根没有
|
||||
`prune` 这个键(不是「值为 undefined」,是键不存在)—— `in` 判存在性,不做属性访问 */
|
||||
@@ -511,7 +523,7 @@ describe('S3 / S4 配方', () => {
|
||||
* 展开)——各写一份早晚分叉,而分叉的那一刻屏上会同时摆着两个互相矛盾的规则。
|
||||
*/
|
||||
it('S3 重算笔与 S2 差异明细创建笔共用同一份 judgement_class / risk_level when 链', () => {
|
||||
const s3Fields = s3[4]?.kind === 'project' ? s3[4].fields : undefined;
|
||||
const s3Fields = s3[3]?.kind === 'project' ? s3[3].fields : undefined;
|
||||
const s2Fields = s2[2]?.kind === 'project' ? s2[2].fields : undefined;
|
||||
expect(s2Fields?.['judgement_class']).toBeDefined();
|
||||
expect(s3Fields?.['judgement_class']).toBe(s2Fields?.['judgement_class']);
|
||||
@@ -532,7 +544,7 @@ describe('S3 / S4 配方', () => {
|
||||
* 「人改四态 → 等级跟着变」这条链不能断),且与 S2 那份是**同一个 when 链引用**(不许分叉)。
|
||||
*/
|
||||
it('**S3 的关系笔只重算判定** —— 收口四格(理由/摘要/风险/验证要求)一列都不许出现,否则人改完一保存就被冲掉', () => {
|
||||
const relation = s3[5];
|
||||
const relation = s3[4];
|
||||
expect(relation?.kind).toBe('project');
|
||||
const fieldsOf = (act: (typeof s3)[number] | undefined): Readonly<Record<string, unknown>> =>
|
||||
act?.kind === 'project' ? act.fields : {};
|
||||
@@ -547,7 +559,7 @@ describe('S3 / S4 配方', () => {
|
||||
/* 主键带回:同 PK 覆盖,不是新开一行 */
|
||||
expect(fields['substitution_relation_id']).toBeDefined();
|
||||
/* 验证记录那一笔仍与 S2 同一份(它不涉及人可改的格子) */
|
||||
expect(s3[6]).toBe(s2[4]);
|
||||
expect(s3[5]).toBe(s2[4]);
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -574,8 +586,8 @@ describe('S3 / S4 配方', () => {
|
||||
/* **排在卡之前**:发了卡这一拍就收了,排在后面的 say 要等下一拍 —— 那时人早看完卡了 */
|
||||
expect(kinds.lastIndexOf('say')).toBeLessThan(kinds.indexOf('card'));
|
||||
/* 只在被打回那一轮说 —— 第一轮那句与卡自己的 ask 是同一件事,退役了 */
|
||||
const redoSay = s3[7];
|
||||
expect(redoSay?.kind === 'say' && redoSay.when).toBe('case_redo_note != ""');
|
||||
const redoSay = s3[6];
|
||||
expect(redoSay?.kind === 'say' && redoSay.when).toBe('case_redo_note != "" and substitution_difference_item_redo_scope_named > 0');
|
||||
const text = redoSay?.kind === 'say' ? redoSay.text : undefined;
|
||||
const parts = JSON.stringify(text);
|
||||
expect(parts).toContain('按你的意见复核了');
|
||||
|
||||
Reference in New Issue
Block a user