Archived
feat(w05): S1 配方 —— 校验入参、铺格、缺什么派一张数据工单
在库与否是一次 lookup 的结果,不是一句断言:先取 found_pn_ref(refersTo 查无此值当场拒)再按它判。 参数骨架两颗料都铺、值回填现值 —— 于是「在库但当年参数没录全」也自动被红带拦住。 发卡的条件只用基座读数:gate_reason 为空串时那一列不写、未绑定,而 S-FEEL 里未绑定的名字 `!= ""` 求值为 true —— 拿它当条件恰恰会在一切正常时把卡发出去。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -113,3 +113,71 @@ describe('剧本声明的读写面 ⊆ 清单声明', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('S1 配方', () => {
|
||||
const recipe = stepOf('S1_inputs')?.auto;
|
||||
const acts = recipe?.do ?? [];
|
||||
const kinds = acts.map((a) => a.kind);
|
||||
|
||||
it('六笔,次序是:两颗料 → 案卷 → 主数据骨架 → 参数骨架 → 卡 → 交代', () => {
|
||||
expect(kinds).toEqual(['project', 'project', 'project', 'project', 'card', 'say']);
|
||||
expect(acts.map((a) => (a.kind === 'project' ? a.into : undefined)).filter(Boolean)).toEqual([
|
||||
'sub_eval.part', 'sub_eval.case', 'parts.profile', 'parts.parameter',
|
||||
]);
|
||||
});
|
||||
|
||||
it('案号由 let 铸,只从本单开案时刻取材', () => {
|
||||
expect(recipe?.let?.['evalRef']).toEqual({ cat: ['W05-', { fmt: { in: 'case:openedAt' }, as: 'digits' }] });
|
||||
});
|
||||
|
||||
it('两颗料由字面两行立起来 —— 这张表是后面每一笔遍历的唯一来源', () => {
|
||||
const part = acts[0];
|
||||
expect(part?.kind === 'project' && 'rows' in part.from && part.from.rows).toHaveLength(2);
|
||||
/* 在库与否是一次 lookup 的结果,不是一句断言:先取依据,再按依据判 */
|
||||
const fields = part?.kind === 'project' ? part.fields : undefined;
|
||||
expect(fields?.['found_pn_ref']).toMatchObject({ lookup: 'parts.profile', take: 'internal_pn' });
|
||||
expect(fields?.['exists_status']).toEqual({ when: 'found_pn_ref != ""', then: 'found', else: 'absent' });
|
||||
});
|
||||
|
||||
it('参数骨架:两颗料都铺、值回填现值 —— 「在库但当年参数没录全」也要被拦住', () => {
|
||||
const params = acts[3];
|
||||
expect(params?.kind === 'project' && params.prune).toBe(true);
|
||||
const from = params?.kind === 'project' ? params.from : undefined;
|
||||
/* 外层是本单两颗料,里层是该类型的字段清单 —— 人没挑类型时 where 一行不命中,什么也不铺 */
|
||||
expect(from).toMatchObject({ each: 'mine:sub_eval.part', then: { each: 'parts.type_param' } });
|
||||
const fields = params?.kind === 'project' ? params.fields : undefined;
|
||||
expect(fields?.['param_value']).toMatchObject({ lookup: 'parts.parameter', take: 'param_value' });
|
||||
});
|
||||
|
||||
it('**发卡的条件全是基座读数** —— 不许拿 `gate_reason != ""` 当条件', () => {
|
||||
const card = acts[4];
|
||||
const when = card?.kind === 'card' ? (card.when ?? '') : '';
|
||||
/*
|
||||
* S-FEEL 里**未绑定的名字 `!= ""` 求值为 true**,而 `gate_reason` 为空串时那一列压根不写、
|
||||
* 于是未绑定 —— 拿它当条件,恰恰在一切正常时把卡发出去。条件只用恒有值的基座读数。
|
||||
*/
|
||||
expect(when).not.toContain('gate_reason');
|
||||
expect(when).toContain('sub_eval_part_exists_status_found < 2');
|
||||
expect(when).toContain('parts_parameter_missing > 0');
|
||||
});
|
||||
|
||||
it('卡:数据工单、四张表都在闸上、且有 basis(基座拒发 basis 为空的工单)', () => {
|
||||
const card = acts[4];
|
||||
expect(card?.kind === 'card' && card.as).toBe('execution');
|
||||
expect(card?.kind === 'card' && card.variant).toBe('data');
|
||||
expect(card?.kind === 'card' && card.gateLedgers).toEqual(['parts.chip_type', 'parts.type_param', 'parts.profile', 'parts.parameter']);
|
||||
expect((card?.kind === 'card' ? card.basis ?? [] : []).length).toBeGreaterThan(0);
|
||||
expect((card?.kind === 'card' ? card.guide ?? [] : []).length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
it('发卡条件与 doneWhen 是同一件事的正反面', () => {
|
||||
const when = acts[4]?.kind === 'card' ? (acts[4].when ?? '') : '';
|
||||
for (const reading of [
|
||||
'sub_eval_part_exists_status_found', 'sub_eval_part_missing',
|
||||
'sub_eval_part_distinct_chip_type_id', 'parts_profile_missing', 'parts_parameter_missing',
|
||||
]) {
|
||||
expect(when).toContain(reading);
|
||||
expect(stepOf('S1_inputs')?.doneWhen).toContain(reading);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user