diff --git a/activities/part-substitution-assessment/__tests__/activity.test.ts b/activities/part-substitution-assessment/__tests__/activity.test.ts index 73dd907..82e1aed 100644 --- a/activities/part-substitution-assessment/__tests__/activity.test.ts +++ b/activities/part-substitution-assessment/__tests__/activity.test.ts @@ -125,6 +125,13 @@ describe('台账声明:授权面与《统一方案》口径', () => { expect(byAddress(D, 'pn_check')?.fields.find((f) => f.name === 'abandon_reason')?.requiredWhen).toEqual({ column: 'exists_status', value: 'abandoned' }); }); + it('判「在库」必须填得出引用依据,基座拿它去主数据里查(2026-08-27 实测:原 PN 不在库而模型把两颗都写成 found,硬阻塞那条路整个被跳过)', () => { + const ref = byAddress(D, 'pn_check')?.fields.find((f) => f.name === 'found_pn_ref'); + expect(ref?.refersTo).toBe('parts.profile.internal_pn'); + expect(ref?.requiredWhen).toEqual({ column: 'exists_status', value: 'found' }); + expect(ref?.primaryKey ?? false).toBe(false); + }); + it('规则版本常量随包冻结', () => { expect(RULE_VERSION).toBe('R-2026.07'); expect(manifest.assets.businessModel.dmn.tables.map((t) => t.id)).toEqual(['d01-difference-status', 'd02-recommendation']); diff --git a/activities/part-substitution-assessment/__tests__/steps.test.ts b/activities/part-substitution-assessment/__tests__/steps.test.ts index d58983a..cf714f2 100644 --- a/activities/part-substitution-assessment/__tests__/steps.test.ts +++ b/activities/part-substitution-assessment/__tests__/steps.test.ts @@ -223,6 +223,35 @@ describe('说明书与剧本的骨架', () => { expect(s0d10).toMatch(/若它还开着|先.*finish/); }); + it('「发卡前清单必须先有行」写在说明书里,不只在剧本里(2026-08-27 实测:模型把 P-10 收步与 S1 发卡压成一拍,那一拍 S1 剧本没摊开,卡发出去时 input_gap 一行都没有,人对着空表无从下手)', () => { + expect(NARRATIVE).toContain('sub_eval.input_gap` 必须先有行'); + expect(NARRATIVE).toMatch(/压拍/); + expect(NARRATIVE).toMatch(/顺序不能反/); + }); + + it('存在性核对的剧本教的是「填得出依据才算在库」,不是「你看着办」(结构治,配 found_pn_ref 那道墙)', () => { + const g = ASSESSMENT_STEPS.find((s) => s.item === 'S0_bind')?.guide ?? ''; + expect(g).toContain('found_pn_ref'); + expect(g).toMatch(/查无此值当场拒/); + expect(g).toMatch(/名单里没有就是没有/); + expect(g).toMatch(/把这一行改成 absent/); + }); + + it('数据工单的名单只认 absent,「建哪几颗」与「要不要问人」分开写(2026-08-27 实测:原 PN 缺库那单,模型把在库的候选派成了数据工单,缺的那颗没人建)', () => { + const g = ASSESSMENT_STEPS.find((s) => s.item === 'S0d_p08_master')?.guide ?? ''; + expect(g).toMatch(/问题一 · 建哪几颗/); + expect(g).toMatch(/问题二 · 建之前要不要先问人/); + expect(g).toMatch(/exists_status = "found" 的一行都不建/); + }); + + it('数据录入卡自带 basis,不让基座兜上内部家具(2026-08-27 实测:没声明 basis 的那张卡,参考信息栏摆的是「这一步 / 当下可为 / 目标缺口」加九个计划项全名)', () => { + const i = NARRATIVE.indexOf('"kind":"entry"'); + expect(i).toBeGreaterThan(-1); + const block = NARRATIVE.slice(i, i + 900); + expect(block).toContain('"basis"'); + expect(block).toContain('触发原因'); + }); + it('五段进度,每段名 ≤10 字;除放行外每项恰属一段', () => { expect(ASSESSMENT_PHASES).toHaveLength(5); for (const p of ASSESSMENT_PHASES) expect(p.name.length, p.name).toBeLessThanOrEqual(10); diff --git a/activities/part-substitution-assessment/src/case-model.ts b/activities/part-substitution-assessment/src/case-model.ts index 8ff9838..24a5a32 100644 --- a/activities/part-substitution-assessment/src/case-model.ts +++ b/activities/part-substitution-assessment/src/case-model.ts @@ -197,7 +197,12 @@ PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**该 {"key":"name","label":"客户名称","value":"","required":true}, {"key":"industry","label":"所属行业"}, {"key":"region","label":"区域"} - ]} + ], + "basis":[{"k":"触发原因","v":"按客户编码 查客户档案,查无记录"}, + {"k":"这一单要评估的","v":"原 PN → 个候选"}, + {"k":"确认之后","v":"这家客户落进本单草稿层,评估照常往下走"}]} +**\`basis\` 这三行必须给**:不给的话基座会兜上「这一步 / 当下可为 / 目标缺口」那几行内部家具 —— +把九个计划项的全名摆到人脸上,他一个字也用不上(2026-08-27 实测)。 人确认那一下基座已把这一行落进草稿层,**你不许再 write 一遍 customer.profile**。 ② 候选不在库、且候选总数 ≥2 时,一颗一张决策卡(按候选数组顺序,一次只发一张;候选总数 = 1 时不问、直接建): @@ -230,7 +235,12 @@ PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**该 "standard":"每颗料号至少一行 datasheet 记录,必填列全齐。", "basis":[{"k":"触发原因","v":"按 internal_pn 查 P-10 查无记录"}]}} -⑤ 执行工单 01 · 评估输入与资料补齐确认(S1_input_confirm;零输入): +⑤ 执行工单 01 · 评估输入与资料补齐确认(S1_input_confirm;零输入)—— +**发这张卡之前,\`${D}.input_gap\` 必须先有行**:清单为空的卡就是让人对着一张空表签字,他补不了任何东西。 +不管这一拍是不是顺手把上一步收掉的(压拍常发生,那时 S1 的剧本还没摊开),写清单与发卡都得在同一串里,顺序不能反: +已绑定四项(customer / request / original_pn / candidate_N,status="ok")、待补两项(application_scenario / bom_position,status="gap")、 +默认三项(substitution_type / rule_version / owner,status="default")、每颗候选的「未披露」参数各一行(status="gap")、原 PN 资料版本(status="ok"); +逐列口径见这一步的剧本。 {"kind":"human","itemId":"S1_input_confirm","workOrder":"execution","card":{ "title":"评估输入与资料补齐确认:<原 PN> → 个候选", "ask":"启动绑定的四项已锁定。请在工作区的件上补齐「待补 / 待确认」行的补充内容,默认值可改可确认。", @@ -296,9 +306,14 @@ request_description(入参原话,不改写)、input_snapshot_ref、original_inte current_stage = "assessing"。application_scenario / bom_position **这一步不写**(启动时未收,S1 由人补)。 【逐个料号查库,写 \`${D}.pn_check\`】\`parts.profile\` 整表在你的作用域里,按 \`internal_pn\` 逐个核对:原 PN 一行(role = "original"), -每个候选一行(role = "candidate");在 → exists_status = "found";不在 → exists_status = "absent"(**这一格先写 absent,等数据工单确认后再改**)。 +每个候选一行(role = "candidate");在 → exists_status = "found" **且 \`found_pn_ref\` 填你在 \`parts.profile\` 里真找到的那一行的 internal_pn** +(与本行 internal_pn 一模一样);不在 → exists_status = "absent"、\`found_pn_ref\` 留空(**等数据工单确认后再改成 created**)。 一颗都不能少:数组里点了几颗候选,就写几行 + 原 PN 一行。note 选填(如「PCN 转 NRND」)。 +**「在库」这句话基座要拿去查**:\`found_pn_ref\` 声明了引用 \`parts.profile.internal_pn\`,写下去那一刻基座就去主数据里查, +**查无此值当场拒**。被拒不是让你换个号去顶 —— 那说明这颗料真的不在库,把这一行改成 absent(依据留空),数据工单那一步的门自己会开。 +判 found 却填不出依据也走不动:这一步的收步条件里有「核对表不缺必填」。**别凭料号长得像就判在库,名单里没有就是没有。** + 【写 \`${D}.verdict\` 一行】eval_ref、request_id、rule_version = "${RULE_VERSION}"、original_internal_pn、candidate_total = 候选数组长度。 写完这一项就结束。有 absent 的 → 下一项是 S0d_p08_master(数据工单),照常往下办。 @@ -319,9 +334,16 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不 【先数两个数,再分岔】(先看轨迹:这一项里已经发过哪些卡、人答了什么,【当前这一步】下面那行引擎事实说得清楚) · **候选总数**:入参 \`candidateInternalPns\` 的长度(\`${D}.pn_check\` 里 role = "candidate" 的行数也是它,不随本单进展变化); · **不在库的候选数**:\`${D}.pn_check\` 里 role = "candidate" 且 exists_status = "absent" 的行数(这个数会随着人放弃/创建而变小)。 -**该不该问人,看的是候选总数,不是不在库的颗数**——**「不在库的只有一颗」不是跳过决策卡的理由**: -· 原 PN absent → **硬阻塞**,不问人,直接进入「建」的名单; -· **候选总数 = 1** → 那一颗不许放弃,不问人,直接进入「建」的名单; +**这里要分开答两个问题,别混成一个**: + +**问题一 · 建哪几颗?** 名单 = \`${D}.pn_check\` 里 **exists_status = "absent" 的每一行**,不管它是原 PN 还是候选。 +**exists_status = "found" 的一行都不建** —— 那几颗的主数据本来就在库里,给它们派数据工单等于让人把已有的东西再填一遍, +而真正缺的那颗还是没人建、单子照样卡死。名单为空(一行 absent 都没有)这一项根本不会出现在你的「当下可为」里。 + +**问题二 · 建之前要不要先问人?** 只有 **absent 的候选**才谈得上问,**看的是候选总数,不是不在库的颗数** +——**「不在库的只有一颗」不是跳过决策卡的理由**: +· 原 PN absent → **硬阻塞**,不问人,直接进名单(它是评估基准,没有它整单无从谈起); +· **候选总数 = 1** → 那一颗候选**如果是 absent** 就不许放弃、不问人,直接进名单(候选是 found 的话它压根不在名单里,没有可问的); · **候选总数 ≥ 2** → **每一颗 absent 的候选都要先发一张决策卡问人**(一次一张,按候选数组顺序;哪怕这一单不在库的候选只有一颗, 只要候选总数 ≥ 2 就要问)照说明书②;人选 "abandon"/"custom:" 的,把那一行 \`${D}.pn_check\` 改成 exists_status = "abandoned" + abandon_reason(同 PK 覆盖写),这颗不再评估;选 "create" 的进「建」的名单。 diff --git a/activities/part-substitution-assessment/src/manifest.ts b/activities/part-substitution-assessment/src/manifest.ts index a952602..14b82d5 100644 --- a/activities/part-substitution-assessment/src/manifest.ts +++ b/activities/part-substitution-assessment/src/manifest.ts @@ -89,7 +89,7 @@ export const manifest: ActivityManifest = { identity: { activityId: ACTIVITY_ID, name: 'ACT-WORK-05 · 差异评估', - packageVersion: '1.2.2', + packageVersion: '1.2.6', category: '需求分析与方案匹配', sdkVersion: '0.1.0-m5', checksum: 'dev-checksum-part-substitution-assessment', @@ -439,6 +439,20 @@ export const manifest: ActivityManifest = { col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true }), col({ name: 'role', type: 'text', label: '角色', required: true, tally: ['original', 'candidate'] }), col({ name: 'exists_status', type: 'text', label: '在库状态', required: true, tally: ['found', 'absent', 'created', 'abandoned'] }), + /* + * 「在库」不是模型说了算的一句话 —— 判 found 就必须把**在主数据里真找到的那个料号**填在这里, + * 基座拿它去 `parts.profile` 查(refersTo),查无此值当场拒。requiredWhen 让这一格只对 found 行必填, + * 而 S0_bind 的 doneWhen 里有 `_missing = 0` —— 于是「判了 found 却填不出依据」这一步就收不掉。 + * 2026-08-27 实测逼出来的:原 PN 根本不在库,模型把两颗都写成 found、交代里还说「都在库」, + * 硬阻塞那条路整个被跳过(剧本早写着「整表在你的作用域里逐个核对」—— 措辞治不了的结构治)。 + */ + col({ + name: 'found_pn_ref', + type: 'text', + label: '在库核对依据', + refersTo: 'parts.profile.internal_pn', + requiredWhen: { column: 'exists_status', value: 'found' }, + }), col({ name: 'abandon_reason', type: 'text', label: '放弃原因', requiredWhen: { column: 'exists_status', value: 'abandoned' } }), col({ name: 'note', type: 'text', label: '备注' }), ], diff --git a/docs/ACT-WORK-05-端到端测试记录.md b/docs/ACT-WORK-05-端到端测试记录.md new file mode 100644 index 0000000..1231f37 --- /dev/null +++ b/docs/ACT-WORK-05-端到端测试记录.md @@ -0,0 +1,224 @@ +# ACT-WORK-05 差异评估 —— 端到端测试记录 + +> 这份文档记的是**每一轮实跑逼出了什么、改了什么、怎么验证的**。 +> 活动包:`activities/part-substitution-assessment`;业务出处 `docs/SS02-ACT-WORK-05-html/`。 +> 台账字段口径见 `docs/数据域字段说明-W05.md`。 +> 全部走查在**浏览器**里做(测试台起单 → 工作台真人点),不是脚本代答。 + +## 一、这个活动是什么 + +原 PN 与候选替代料之间**逐项差异评估**:比参数、判四态(MATCH / MISMATCH / UNKNOWN / CONDITIONAL)、 +定推荐等级(preferred / alternate / conditional / not_recommended)、留证据与验证任务, +最后把结论落进 P-11(替代关系 + 差异明细)并回写 P-01 需求,发射 `assessment-released` 交 ACT-GATE-03 评审。 + +**九个计划项**(五处不给 doneWhen —— 触达人的一律交人收): + +| 项 | 谁办 | 卡 / 件 | doneWhen | +|---|---|---|---| +| `S0_bind` | AI | —— | 有 | +| `S0d_p08_master` | 人 · 数据工单 | `p08-master-create`(表1 芯片主数据 + 表2 标准化参数集) | 无 | +| `S0d_p10_document` | 人 · 数据工单 | `p10-document-create`(原厂技术资料) | 无 | +| `S1_input_confirm` | 人 · 执行工单 | `eval-input-list`(评估输入清单) | 无 | +| `S2_normalize` | AI | —— | 有 | +| `S3_assess` | AI + 两枚工具 | —— | 有 | +| `S4_review` | 人 · 决策卡(可循环) | `difference-review` | 无 | +| `S5_prepare_landing` | AI | —— | 有 | +| `T_release` | 人 · 终局验收 | `landing-confirm` | 无 | + +## 二、逐轮走查 + +### R1 · 样例「都在库·常路」 + +**第 1 次 —— 熔断**,逮到两条真缺陷: + +- 轨迹里 `S0_bind` 写完三张表后连续 5 拍只做一件事「弃用计划项 `S0d_p08_master`」,每拍被拒 + 「生命周期迁移非法:Available → Disabled」→ 熔断交人;工作区还摆着一张「待建 0 条记录」的空数据工单件。 +- **根因一(平台事实,本轮不改)**:`lifecycle.ts` 里 `Disabled` 只能从 `Enabled` 来,而 `Enabled` 只由 + `plan` 动作写;普通计划项一直停在 `Available`。全仓只有本活动用过 `disable` —— **这条路从没人走通过**。 +- **根因二(活动缺陷,已修)**:哨兵 `s_bound` 只看「需求写了 + 核对表写了」,而 `S0_bind` 在**每条路**上都写这两张表, + 于是都在库时数据工单那两项照样进「当下可为」,剧本就让模型去 disable。 +- 修:哨兵加 `sub_eval_pn_check_exists_status_absent > 0`;全包清 `disable`;两条回归测试。**v1.0.1**。 + +**第 2 次 —— 通了**:两拍写完 `demand.request` / `pn_check` / `verdict`,直接派出执行工单; +提交闸如实拦住空手交卷并点名三条缺项(与件上红带同名同数);填三格保存 → 闸自收 → 提交。 + +**收工:`released` · 12 拍 · 人环 3 · 行动被拒 0**。落账逐条 SQL 核过: + +- P-11 主记录 1 条:`pin_to_pin | alternate | fae_confirmed | 客批=(空) | R-2026.07` —— **客户批准状态确实留空**(只读边界守住),规则版本随结论落账; +- P-11 差异明细 9 条(8 MATCH / 1 CONDITIONAL);P-01 回写五项齐;出口 `assessment-released` 已发射; +- 验收卡 11 条判据全部达标(L1×2 / L2×6 / L4×3),一次通过。 + +### R2 · 样例「候选缺库·派数据工单」 + +四次开跑,逼出四条缺陷(E / F / G / H),逐条治了再跑;第 4 次(v1.2.3)全绿。 + +| 次 | 版本 | 结果 | 逼出什么 | +|---|---|---|---| +| 1 | 1.2.0 | 中途停 | E(决策卡被跳过)已在本次前修好并当场验证;F 现形 | +| 2 | 1.2.1 | 熔断 | F 修复通过;**G 现形**(`recommend` 工具被跳过 → S3 收步恒假) | +| 3 | 1.2.2 | 中途停 | G 修复通过;**H 现形**(压拍发卡,清单一行没有) | +| 4 | 1.2.3 | **`released` · 被拒 0 · 人环 5** | 四条修复全部实测通过 | + +第 4 次逐段实证: + +- 决策卡按规格出现(候选总数 2 ≥ 2)→ 答「创建主数据」; +- P-08 数据工单**一件两表**(主数据 1 行 + 参数 6 行),两张表各有自己的编辑闸与缺项红带; + 填好保存 → 提交 → **画面自己切到 P-10**(发现 F 的修复稳定); +- **执行工单出来时清单已有 12 行**(上一轮是 0)—— 发现 H 的修复见效;提交闸如实拦住剩下那条 + 「未披露」缺口,补上才放行 —— 闸与件同源; +- **S3 两枚工具都真调了**(轨迹里 `compare-parameters 返回` / `recommend 返回` 各一条),自动收步,没有熔断; +- **审核驳回循环**(本轮顺带覆盖):在「写下你的口径」里写两条具体意见 → 模型改台账 → **换问法重发**新卡。 + 逐条核过落点:XL2596S-5.0 降 `conditional`、两条 UNKNOWN 明细写明处置(挂 `VAL-…` 验证任务、 + 「闭环前不升级推荐等级」)、LM2596S-ADJ 的推荐理由点名「R1/R2 分压电阻」—— **意见落在被点名的那两处**; +- 终局验收 11 条判据全部达标 → 确认通过 → `released`。 + +落账 SQL 复核:P-11 两条关系(`pin_to_pin | conditional | fae_confirmed` / `pin_to_pin | alternate | fae_confirmed`, +客批均空、规则版本 `R-2026.07`)· 差异明细 18 条(15 MATCH / 2 UNKNOWN / 1 CONDITIONAL)· +验证任务 1 条(`not_started`)· P-01 回写五项齐 · **XL2596S-5.0 真进了主数据**(`XLSEMI | active`,6 行参数 + 1 份资料)。 + +### R3 · 样例「三候选·含硬约束不推荐」—— `released` · 被拒 0 + +一次跑通,验的是**硬约束这条路**: + +- 决策卡按规格出现(3 颗候选,只有 CN-DCDC-32V 缺库)→ 建主数据 → P-08 填 + **输入电压范围 4.5~32(hard)**、工作温度 -20~+85(hard); +- 清单 13 行、三行 default 的 detail 写对了默认值本身;缺口里把客户约束写成可解析式「输入上限硬指标 >= 40(V)」; +- S2 铺 3 条关系 + 27 条明细;S3 两枚工具都调过; +- **CN-DCDC-32V = `not_recommended` / high**(两条硬约束 MISMATCH:输入 4.5~32 vs 4.5~40、温域 -20 vs -40), + XL2596S-5.0 `conditional`、LM2596S-ADJ `alternate`; +- 验收件上三颗候选各一份「拟写 P-11」字段表,徽标逐个核过(不推荐/高 · 条件推荐/中 · 备选/中 · Pin-to-Pin · FAE确认)。 + +**一次自纠**:我先按 `innerText` 判定「四个枚举列是空的」,险些记成缺陷 —— +徽标文字是 CSS `::after` 生成的,`innerText` 读不到。**验证呈现要看 computed style,不看 innerText**。 + +### R4 · 样例「原PN缺库·硬阻塞」 + +三次开跑,逼出两条**同族**的真缺陷: + +**第 1 次(v1.2.3)—— 发现 I:「在库」是模型自报的一句话** + +原 PN `LM2596S-3.3` 主数据里**一行都没有**(SQL 复核 count = 0),模型把原 PN 与候选**两颗都写成 `found`**, +交代里还写「原 PN 与候选都在库」—— 数据工单一张没派,直接把评估输入执行工单发给了人。 +剧本早写着「`parts.profile` 整表在你的作用域里,按 internal_pn 逐个核对」——**措辞已到头**。 + +**结构治**:`sub_eval.pn_check` 加一列 `found_pn_ref`, +`refersTo: parts.profile.internal_pn` + `requiredWhen: {exists_status: found}`。 +于是「判在库」这句话必须配一个**基座拿去主数据里查得到**的料号: + +- 查无此值 → `draftWrite` 当场拒; +- 填不出 → `pn_check_missing > 0` → `S0_bind` 的 `doneWhen` 收不掉。 + +两条路都堵死,唯一走得通的就是如实写 `absent`。**这是「机检只读基座数」第六次现形** +(前五次是 missing_key_total / missing_minor_total / untyped_total / draft_total / 哨兵读 line_total), +这次的形状不是计数,是**存在性**。 + +**第 2 次(v1.2.4)—— 墙生效了,但逮出发现 J:数据工单派给了在库的那颗** + +核对表这次写对了(`LM2596S-3.3 = absent`、`LM2596S-ADJ = found` 且依据经基座查过), +可数据工单派给了 **LM2596S-ADJ**(它在库、参数资料齐全),真正缺的那颗没人建。 +交代原话:「候选只有 LM2596S-ADJ 一颗,按规则直接建主数据」—— +它把「候选总数 = 1 → 不问人」这条**关于问不问**的规则,读成了**关于建哪颗**的规则。 +病根:剧本把两个问题写在同一串项目符号里,三档分岔每档都以「→ 直接进入建的名单」收尾, +而**从没写过「found 的不建」**。与发现 E 同形。 + +**治法(措辞,精准化)**:拆成「**问题一 · 建哪几颗**」(名单 = `absent` 的每一行,`found` 的一行都不建,并写明后果) +与「**问题二 · 建之前要不要先问人**」(只有 absent 的候选才谈得上问)。这一处不上结构 —— +系统其实兜得住(原 PN 还 absent → S1 哨兵不开 → 依赖断点),只是要白烧一次人环; +而这一刀是**补上从没写过的那句话**,不是把已经写过的话再说一遍。 + +**第 3 次(v1.2.5)—— `released`**:数据工单派给了 `LM2596S-3.3`,**没发决策卡**(硬阻塞不问人),两条修复都实测通过。 + +这一轮还留下了**发现 I 那道墙开火的现场证据**(轨迹 seq 6): + +> 行动被拒:在库核对依据「LM2596S-3.3」在芯片主数据(P-08)里不存在 —— 这一列只许引用台账里真实存在的值…… + +模型确实又想判「在库」,基座当场拒回并点名;它随后如实写了 `absent`,硬阻塞照规格走了下去。 + +**终局「重做」分支也在这一轮验了**(本活动此前未验):在验收卡上写明两条意见后点「重做」—— +模型没有原样再递一次,而是**真去改了台账**(`demand.request.verification_requirement` 重写、 +5 行 `difference_item.evidence_ref` 从「只写文件名」改成指到 `Rev H (2023-09) §6.5 Electrical Characteristics`), +再重新裁量 + 提交;第二张验收卡确认通过 → `released`。 + +整单 2 次被拒:一次是上面那道墙(它该拒),一次是 S3 的「先 finish 后调工具」—— +拒回文案照旧只谈「行与列」(平台欠账已记),但模型下一拍自己去调了 `recommend`、随即自动收步,没有熔断。 + +落账:`parts.profile.LM2596S-3.3`(TI / nrnd,7 行参数)真进了主数据; +P-11 一条(`LM2596S-3.3 → LM2596S-ADJ | alternate | fae_confirmed`);P-01 回写齐。 + +### R5 · 样例「新客户·先建档」 + +验的是**数据录入卡**这条路(客户不在档案里,先收人、再评估): + +- 卡按规格出现:「客户 CU-9001 不在客户档案里,请核对建档信息后确认」,客户名称已从入参带出、行业与区域留白给人补; +- 补两格 → 确认写入 → 客户档案落草稿层(`深圳恒星电源有限公司 / 工业电源 / 华南`)→ 流程直奔 S1(两颗料都在库); +- 缺口补齐 → 审核通过 → 终局确认 → `released`。 + +**发现 K**:那张录入卡的「参考信息」栏摆的是引擎兜底的**内部家具** —— +「这一步 / 当下可为 / 目标缺口」,后面跟着**九个计划项的全名**,人一个字用不上。 +病根:`entryCard.basis` 在契约里是**可选**的,活动的卡骨架没声明,基座就把内部事实兜了上去。 +与 A1 2026-08-03 那次同族(那次是执行卡的 steps/standard 没进说明书)。 +治法:说明书里的录入卡骨架补 `basis` 三行(触发原因 / 这一单要评估的 / 确认之后会发生什么), +并在骨架下写明「不给基座会兜内部家具」。配一条测试。 + +**一处操作教训(不是缺陷)**:测试台在「清场」之后会回落到样例 0 —— +选样例与「起单试跑」必须在同一次操作里完成,否则起的是上一个默认样例。 + +## 二·五、六轮走查一览 + +| 轮 | 样例 | 结果 | 这一轮覆盖的分支 | +|---|---|---|---| +| R1 | 都在库·常路 | `released` · 12 拍 · 人环 3 · 被拒 0 | 无数据工单直通;验收判据 11/11 | +| R2 | 候选缺库·派数据工单 | `released` · 被拒 0 · 人环 5 | 决策卡(创建)· P-08 一件两表 · P-10 · **审核驳回循环** | +| R3 | 三候选·含硬约束不推荐 | `released` · 被拒 0 | 三候选并行 · **硬约束 MISMATCH → `not_recommended`** | +| R4 | 原PN缺库·硬阻塞 | `released` · 被拒 2(其中一次是墙该拒) | **硬阻塞不问人** · **终局「重做」循环** | +| R5 | 新客户·先建档 | `released` · 被拒 1 | **数据录入卡建档** | +| R5b | 新客户(v1.2.6) | 验完即停 | 录入卡 `basis` 呈现 | + +版本轨迹:1.0.0 → 1.0.1(A)→ 1.1.0/1.1.1(B·C·D)→ 1.2.0(参数子表)→ 1.2.1(F)→ 1.2.2(G) +→ 1.2.3(H)→ 1.2.4(I)→ 1.2.5(J)→ **1.2.6(K)**。包内自测 16 → **61 条**。 + +## 三、走查发现总表 + +| # | 现象 | 病根 | 治法 | 层 | +|---|---|---|---|---| +| A | 缺口表**每一行**都摆红「必填」,而闸只认 3 条 | 那一格用静态 `req` class,`supplement` 的声明是 `requiredWhen:{status:'gap'}` | 按行投影 `data-class-of="status"`,判断留样式表 | 活动 | +| B | 五张模版是 E04/I01 的 `jc-rp-*` 外壳,不是目标件的版面 | 计划文本自相矛盾(一边说逐字量自目标件,一边让用 `prd-style.ts`) | 目标件 `