Archived
feat(w05): S3 审核卡与 S4 落账放行 —— 摘要只读,改的是四态
关系与验证记录的投影抽成模块级常量,S2 与 S3 共用同一份: 人改了某一条四态,等级 / 风险 / 摘要 / 验证要求跟着重算。 各写一份早晚分叉,而分叉那一刻屏上会同时摆着两个互相矛盾的等级。 次序是投影在前、卡在后:人交回后卡这一笔只跳过自己,投影仍跑 —— 重算才发生在该发生的那一刻。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -297,3 +297,44 @@ describe('S2 配方 —— AI 只出场一次', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('S3 / S4 配方', () => {
|
||||
const s3 = stepOf('S3_review')?.auto?.do ?? [];
|
||||
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', 'card', 'say']);
|
||||
expect(s3[0]?.kind === 'project' && s3[0].into).toBe('substitution.relation');
|
||||
expect(s3[1]?.kind === 'project' && s3[1].into).toBe('validation.record');
|
||||
});
|
||||
|
||||
it('**S2 与 S3 用的是同一份投影** —— 各写一份早晚分叉', () => {
|
||||
expect(s3[0]).toBe(s2[2]);
|
||||
expect(s3[1]).toBe(s2[3]);
|
||||
});
|
||||
|
||||
it('S3 的卡:审核面孔、单一选项、挂本步的件', () => {
|
||||
const card = s3[2];
|
||||
expect(card?.kind === 'card' && card.as).toBe('decide');
|
||||
expect(card?.kind === 'card' && card.variant).toBe('review');
|
||||
expect(card?.kind === 'card' ? card.options ?? [] : []).toHaveLength(1);
|
||||
expect((card?.kind === 'card' ? card.options ?? [] : [])[0]?.value).toBe('approve');
|
||||
expect((card?.kind === 'card' ? card.basis ?? [] : []).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('S4 两笔:写结论一句话 → 提交放行', () => {
|
||||
expect(s4.map((a) => a.kind)).toEqual(['project', 'submit']);
|
||||
expect(s4[0]?.kind === 'project' && s4[0].into).toBe('sub_eval.case');
|
||||
expect(s4[1]?.kind === 'submit' && typeof s4[1].rationale !== 'undefined').toBe(true);
|
||||
});
|
||||
|
||||
it('`submit` 所在的步声明了人工验收清单 —— 系统提交不比模型提交宽松', () => {
|
||||
expect((stepOf('T_release')?.acceptanceChecklist ?? []).length).toBeGreaterThanOrEqual(4);
|
||||
});
|
||||
|
||||
it('两步都不给 doneWhen —— 收步由人答卡决定', () => {
|
||||
expect(stepOf('S3_review')?.doneWhen).toBeUndefined();
|
||||
expect(stepOf('T_release')?.doneWhen).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,6 +110,90 @@ export const NARRATIVE = `§0 本活动只回答一件事:**候选内部料号
|
||||
· 硬约束 MISMATCH 不许被措辞改判成 MATCH,要改只能补证据后重比;
|
||||
· 不写客户批准状态——那是独立审批流的事,本活动只答技术可替代性。`;
|
||||
|
||||
/*
|
||||
* **关系与验证记录的投影,S2 与 S3 共用同一份**。
|
||||
* S2 算出第一版;人在审核件上改了某一条四态之后,S3 用**同一份**重算 ——
|
||||
* 各写一份早晚分叉,而分叉的那一刻屏上会同时摆着两个互相矛盾的等级。
|
||||
*/
|
||||
const RELATION_PROJECTION = {
|
||||
kind: 'project' as const,
|
||||
into: 'substitution.relation',
|
||||
from: { once: true as const },
|
||||
fields: {
|
||||
substitution_relation_id: { in: 'relationId' },
|
||||
eval_ref: { in: 'evalRef' },
|
||||
original_internal_pn: { in: 'originalPn' },
|
||||
candidate_internal_pn: { in: 'candidatePn' },
|
||||
chip_type_id: { in: 'typeId' },
|
||||
recommendation_level: {
|
||||
when: 'substitution_difference_item_judgement_class_hard_mismatch > 0', then: 'not_recommended',
|
||||
else: {
|
||||
when: 'substitution_difference_item_judgement_class_unknown > 0', then: 'conditional',
|
||||
else: {
|
||||
when: 'substitution_difference_item_judgement_class_conditional > 0', then: 'conditional',
|
||||
else: { when: 'substitution_difference_item_judgement_class_soft_mismatch > 0', then: 'conditional', else: 'recommended' },
|
||||
},
|
||||
},
|
||||
},
|
||||
risk_level: {
|
||||
when: 'recommendation_level = "not_recommended"', then: 'high',
|
||||
else: { when: 'recommendation_level = "recommended"', then: 'low', else: 'medium' },
|
||||
},
|
||||
difference_summary: {
|
||||
cat: [
|
||||
'共 ', { in: 'substitution_difference_item_rows' }, ' 项核心参数:',
|
||||
{ in: 'substitution_difference_item_judgement_class_match' }, ' 项一致 / ',
|
||||
{ in: 'substitution_difference_item_judgement_class_hard_mismatch' }, ' 项硬约束不符 / ',
|
||||
{ in: 'substitution_difference_item_judgement_class_soft_mismatch' }, ' 项软约束不符 / ',
|
||||
{ in: 'substitution_difference_item_judgement_class_unknown' }, ' 项待确认 / ',
|
||||
{ in: 'substitution_difference_item_judgement_class_conditional' }, ' 项有条件。',
|
||||
],
|
||||
},
|
||||
recommendation_reason: {
|
||||
when: 'recommendation_level = "not_recommended"',
|
||||
then: '有硬约束不满足 —— 逐条见差异明细里判为 MISMATCH 且约束类型为硬的那几行;硬性边界不满足,不因措辞改判。',
|
||||
else: {
|
||||
when: 'recommendation_level = "recommended"',
|
||||
then: '每一项核心参数都判为一致,没有待确认项,可直接替代。',
|
||||
else: '没有硬约束不满足,但仍有待确认或需附条件的项 —— 逐条见差异明细;闭环之后方可采用。',
|
||||
},
|
||||
},
|
||||
verification_requirement: {
|
||||
when: 'recommendation_level = "conditional"',
|
||||
/* `{joinRows}` 未实现 —— 不逐个列参数名,指向明细表(那里逐条摆着,且人能就地改) */
|
||||
then: '实测差异明细里判为 UNKNOWN 与 CONDITIONAL 的每一项,并把实测值回填 P-08 标准化参数集;回填后重跑本评估。',
|
||||
else: '',
|
||||
},
|
||||
validation_id: { cat: ['VAL-', { in: 'evalRef' }] },
|
||||
analysis_status: 'fae_confirmed',
|
||||
rule_version: RULE_VERSION,
|
||||
},
|
||||
} as const;
|
||||
|
||||
/* P-06 **一单固定一条**(用户定)。本活动只写「还得去验什么」,状态一律 `not_started`:它不是实测结果,**不许虚构实测**。 */
|
||||
const VALIDATION_PROJECTION = {
|
||||
kind: 'project' as const,
|
||||
into: 'validation.record',
|
||||
from: { once: true as const },
|
||||
fields: {
|
||||
validation_id: { cat: ['VAL-', { in: 'evalRef' }] },
|
||||
eval_ref: { in: 'evalRef' },
|
||||
validation_internal_pn: { in: 'candidatePn' },
|
||||
validation_type: 'parametric',
|
||||
validation_status: 'not_started',
|
||||
test_condition: {
|
||||
when: 'substitution_difference_item_judgement_class_unknown + substitution_difference_item_judgement_class_conditional > 0',
|
||||
then: '差异明细里判为 UNKNOWN 与 CONDITIONAL 的每一项,按原厂规格书条件实测。',
|
||||
else: '无',
|
||||
},
|
||||
next_validation_action: {
|
||||
when: 'substitution_difference_item_judgement_class_unknown + substitution_difference_item_judgement_class_conditional > 0',
|
||||
then: '实测上述各项并把实测值回填 P-08 标准化参数集,回填后重跑本评估。',
|
||||
else: '本轮无待验项。',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 步骤剧本 —— 一个计划项一条。四步**全程零 `guide`、零工具**:行为改由 `auto` 配方接管
|
||||
* (ADR-014),这一份只放骨架 —— 每步 `auto.do` 先各摆一笔 `say` 占位,配方的实体
|
||||
@@ -420,92 +504,19 @@ export const ASSESSMENT_STEPS = [
|
||||
rule_version: RULE_VERSION,
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ③ 关系一行 —— **等级、风险、摘要、理由全由规则与模板拼,不让 AI 编**。
|
||||
* 于是永远不会出现「有硬约束不符却写着推荐」;人在审核件上改了某一条四态,这一笔重跑,等级跟着变。
|
||||
*/
|
||||
{
|
||||
kind: 'project' as const,
|
||||
into: 'substitution.relation',
|
||||
from: { once: true as const },
|
||||
fields: {
|
||||
substitution_relation_id: { in: 'relationId' },
|
||||
eval_ref: { in: 'evalRef' },
|
||||
original_internal_pn: { in: 'originalPn' },
|
||||
candidate_internal_pn: { in: 'candidatePn' },
|
||||
chip_type_id: { in: 'typeId' },
|
||||
recommendation_level: {
|
||||
when: 'substitution_difference_item_judgement_class_hard_mismatch > 0', then: 'not_recommended',
|
||||
else: {
|
||||
when: 'substitution_difference_item_judgement_class_unknown > 0', then: 'conditional',
|
||||
else: {
|
||||
when: 'substitution_difference_item_judgement_class_conditional > 0', then: 'conditional',
|
||||
else: { when: 'substitution_difference_item_judgement_class_soft_mismatch > 0', then: 'conditional', else: 'recommended' },
|
||||
},
|
||||
},
|
||||
},
|
||||
risk_level: {
|
||||
when: 'recommendation_level = "not_recommended"', then: 'high',
|
||||
else: { when: 'recommendation_level = "recommended"', then: 'low', else: 'medium' },
|
||||
},
|
||||
difference_summary: {
|
||||
cat: [
|
||||
'共 ', { in: 'substitution_difference_item_rows' }, ' 项核心参数:',
|
||||
{ in: 'substitution_difference_item_judgement_class_match' }, ' 项一致 / ',
|
||||
{ in: 'substitution_difference_item_judgement_class_hard_mismatch' }, ' 项硬约束不符 / ',
|
||||
{ in: 'substitution_difference_item_judgement_class_soft_mismatch' }, ' 项软约束不符 / ',
|
||||
{ in: 'substitution_difference_item_judgement_class_unknown' }, ' 项待确认 / ',
|
||||
{ in: 'substitution_difference_item_judgement_class_conditional' }, ' 项有条件。',
|
||||
],
|
||||
},
|
||||
recommendation_reason: {
|
||||
when: 'recommendation_level = "not_recommended"',
|
||||
then: '有硬约束不满足 —— 逐条见差异明细里判为 MISMATCH 且约束类型为硬的那几行;硬性边界不满足,不因措辞改判。',
|
||||
else: {
|
||||
when: 'recommendation_level = "recommended"',
|
||||
then: '每一项核心参数都判为一致,没有待确认项,可直接替代。',
|
||||
else: '没有硬约束不满足,但仍有待确认或需附条件的项 —— 逐条见差异明细;闭环之后方可采用。',
|
||||
},
|
||||
},
|
||||
verification_requirement: {
|
||||
when: 'recommendation_level = "conditional"',
|
||||
/* `{joinRows}` 未实现 —— 不逐个列参数名,指向明细表(那里逐条摆着,且人能就地改) */
|
||||
then: '实测差异明细里判为 UNKNOWN 与 CONDITIONAL 的每一项,并把实测值回填 P-08 标准化参数集;回填后重跑本评估。',
|
||||
else: '',
|
||||
},
|
||||
validation_id: { cat: ['VAL-', { in: 'evalRef' }] },
|
||||
analysis_status: 'fae_confirmed',
|
||||
rule_version: RULE_VERSION,
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ④ 验证记录一行 —— **一单固定一条**(用户定)。本活动只写「还得去验什么」,
|
||||
* 状态一律 `not_started`:它不是实测结果,**不许虚构实测**。
|
||||
*/
|
||||
{
|
||||
kind: 'project' as const,
|
||||
into: 'validation.record',
|
||||
from: { once: true as const },
|
||||
fields: {
|
||||
validation_id: { cat: ['VAL-', { in: 'evalRef' }] },
|
||||
eval_ref: { in: 'evalRef' },
|
||||
validation_internal_pn: { in: 'candidatePn' },
|
||||
validation_type: 'parametric',
|
||||
validation_status: 'not_started',
|
||||
test_condition: {
|
||||
when: 'substitution_difference_item_judgement_class_unknown + substitution_difference_item_judgement_class_conditional > 0',
|
||||
then: '差异明细里判为 UNKNOWN 与 CONDITIONAL 的每一项,按原厂规格书条件实测。',
|
||||
else: '无',
|
||||
},
|
||||
next_validation_action: {
|
||||
when: 'substitution_difference_item_judgement_class_unknown + substitution_difference_item_judgement_class_conditional > 0',
|
||||
then: '实测上述各项并把实测值回填 P-08 标准化参数集,回填后重跑本评估。',
|
||||
else: '本轮无待验项。',
|
||||
},
|
||||
},
|
||||
},
|
||||
RELATION_PROJECTION,
|
||||
VALIDATION_PROJECTION,
|
||||
{ kind: 'say' as const, text: '逐条差异结论已备好,推荐等级由规则按四态算出。' },
|
||||
],
|
||||
/*
|
||||
* **`] as const` 是必须的,不是随手加的**:`do` 数组里一旦混入对已命名常量的
|
||||
* *引用*(`RELATION_PROJECTION`/`VALIDATION_PROJECTION`),TS 对这个数组字面量的
|
||||
* 逐位置精确类型推断就整体失效 —— 连数组里其它仍是内联字面量的位置(如 `acts[1]`)
|
||||
* 也会被拉平成「这个数组里出现过的所有形状的并集」,`fields['具体列名']` 当场报
|
||||
* TS7053(实测:去掉这个 `as const`,`pnpm typecheck` 在 `S2 配方` describe 块
|
||||
* 报 9 处这类错误,尽管 `vitest run` 不做类型检查、照样能跑绿 —— 这是本仓已有的
|
||||
* `: {}` vs `: undefined` 那类「并集吞窄类型」陷阱的第二种触发形态)。
|
||||
*/
|
||||
] as const,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -523,7 +534,28 @@ export const ASSESSMENT_STEPS = [
|
||||
'判 UNKNOWN 的都写了去向:实测哪一项、回填到哪儿',
|
||||
'影响说明站得住:说的是「换上去会不会比现在差」,不是复述参数值',
|
||||
],
|
||||
auto: { do: [{ kind: 'say' as const, text: '差异结论已备好,请过目。' }] },
|
||||
auto: {
|
||||
do: [
|
||||
/* 人改了四态,等级 / 风险 / 摘要 / 验证要求跟着重算 —— 摘要只读,改的是四态 */
|
||||
RELATION_PROJECTION,
|
||||
VALIDATION_PROJECTION,
|
||||
{
|
||||
kind: 'card' as const,
|
||||
as: 'decide' as const,
|
||||
variant: 'review' as const,
|
||||
title: '差异评估结论审核',
|
||||
ask: '逐条差异结论已备好。请在工作区的件上逐条核对;要改的就地改(四态、影响说明、判断依据都可改),改完回到这里确认。',
|
||||
options: [{ value: 'approve', label: '确认通过', detail: '逐条核对无误,可以进入落账放行' }],
|
||||
basis: [
|
||||
{ k: '原料号', v: { lookup: 'mine:sub_eval.part', by: { role: 'original' }, take: 'internal_pn', else: '' } },
|
||||
{ k: '候选料号', v: { lookup: 'mine:sub_eval.part', by: { role: 'candidate' }, take: 'internal_pn', else: '' } },
|
||||
{ k: '本轮结论', v: { lookup: 'mine:substitution.relation', by: {}, take: 'difference_summary', else: '' } },
|
||||
],
|
||||
},
|
||||
{ kind: 'say' as const, text: '差异结论已送审,请在件上逐条核对。' },
|
||||
/* `] as const`:这个数组也引用了 RELATION_PROJECTION/VALIDATION_PROJECTION —— 理由同 S2 的 do 数组头上那条注释 */
|
||||
] as const,
|
||||
},
|
||||
},
|
||||
{
|
||||
item: 'T_release' as const,
|
||||
@@ -541,7 +573,36 @@ export const ASSESSMENT_STEPS = [
|
||||
writes: ['sub_eval.case'],
|
||||
reads: ['substitution.relation', 'substitution.difference_item', 'validation.record', 'sub_eval.case'],
|
||||
tools: [],
|
||||
auto: { do: [{ kind: 'say' as const, text: '拟落账内容已备好,请核对后签字。' }] },
|
||||
auto: {
|
||||
do: [
|
||||
{
|
||||
kind: 'project' as const,
|
||||
into: 'sub_eval.case',
|
||||
from: { once: true as const },
|
||||
fields: {
|
||||
eval_ref: { lookup: 'mine:sub_eval.case', by: {}, take: 'eval_ref', else: '' },
|
||||
summary: {
|
||||
cat: [
|
||||
{ lookup: 'mine:substitution.relation', by: {}, take: 'candidate_internal_pn', else: '' },
|
||||
' 替代 ',
|
||||
{ lookup: 'mine:substitution.relation', by: {}, take: 'original_internal_pn', else: '' },
|
||||
':',
|
||||
{ lookup: 'mine:substitution.relation', by: {}, take: 'difference_summary', else: '' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'submit' as const,
|
||||
rationale: {
|
||||
cat: [
|
||||
'逐项差异评估已经审核通过:',
|
||||
{ in: 'substitution_difference_item_rows' }, ' 条明细、1 条替代关系、1 条验证记录待落账。',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user