This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-TMP-REPO/docs/数据域字段说明-W05.md
xcsandClaude Opus 5 8586f7dac8 docs(w05): 重写收口 —— 头注核正、字段说明按现状重写、规范补四条 auto 纪律、CLAUDE.md 记战果
Task 12 的非走查部分(浏览器走查卡在登录、归人工,不在本次范围)。

① index.ts 头注仍写着旧的「三条技能 + 五张模版」,重写后是 0 技能 3 张件,核正
(Ruling H25);包内其余「三条技能/五张/两枚工具/九步」等提法逐处核过,均为带日期的
「从…改成…」历史叙述,不是失实断言,未动。

② docs/数据域字段说明-W05.md 整篇重写(不是打补丁)。旧文档写的是重写前的十张表
(customer.profile/parts.document/demand.request 与 sub_eval 三张旧单据,本活动这次
重写已不再声明),现按 manifest.ts 的 declarations.ledger 逐表核对重写:9 张表、66 列、
20 人填列。写入者词表另定一套(人(件上)/AI(S2 唯一一次 analyze)/配方(project 算出来
的)/基座)——E04/I01 那套多轮 agent 编排的词表在这里说不清楚。逐个 data-edit 核对过
report.ts 才确认写入者归属:S3 的四态等列改成人在件上直接编辑(不再是「人写意见→AI 改」),
sub_eval.part/case、substitution.relation、validation.record 四张表没有任何人工编辑
入口、全部配方算。顺手核了 seed-mvp-data.ts 的基线物理列,注了每张表里这次重写不再
声明的旧列(旧九步版本留下的痕迹),并记下 type_id 这条身份链路是本仓唯一一处
integer→text 加宽声明。

③ docs/活动开发规范.md §4.6 补四条纪律(六条→十条):jsonb 会重排 fields 键序、字段
求值改按依赖拓扑排序不能拿书写顺序当依赖顺序;跨笔顺序仍要自己排(一笔读的是另一笔
写出来的数就要排后面);两笔方向相反的依赖拆两笔写中间夹子表;gateLedgers 同时管
「闸要求非空」与「确认时并进正式表」两件事,漏想一半会悬空引用或永久卡死。§6.5 补第 9
条「测试要能咬住」(镜像测试与读真文件测试证明的是两件事;变异测试本身可能是空的;
能 import 真东西就别手抄)。§7 补两行排障(reset-runs 前先捞证据;dev 进程可能在跑
几小时前的代码,连 tsup --watch 监视进程本身都可能已死)。八条内容逐条去任务报告核过
原始事故与提交号,没有凭印象转述。

④ CLAUDE.md 现状快照追加一条:四步全 auto、两张类型字典、三张件、退役清单;e2e 三例
released 被拒 0(候选缺库/都在库/原PN缺库,模型调用恰 1 次);类型不一致那例撞上已记
debt.md 的 prune 死锁,两颗都缺库那例卡在「答完卡不推进」、真因未定位如实记录;
judgement_class 7/7 逐行核对通过;jsonb 键序地基缺陷的发现与修法(拓扑排序,6636abf)。
顺手核正文档地图里 W05 那一行的旧字段数(十张/110列→九张/66列,与②同步)。

pnpm gates 退出码 0(2447 passed | 34 skipped),与 Task 11 最后一轮记录一致,无回归。
未碰 manifest.ts 里那处与本任务无关的未提交版本号改动(2.1.5→2.1.6,疑似上一个卡在
登录的浏览器走查会话留下)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 11:22:56 +08:00

473 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 数据域字段说明 —— ACT-WORK-05 差异评估
> 对应设计中心 → 数据域「ACT-WORK-05 · 差异评估」分组。字段以活动清单的台账声明为准
> (`activities/part-substitution-assessment/src/manifest.ts` 的 `declarations.ledger`)。
> **2026-08-29 整体重写**(design spec `docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md`)
> 把九步案例模型砍成四步、全程零 `guide`(行为改由 `PlanStep.auto` 声明式配方接管,ADR-014),
> 台账从旧的十张收成**九张**:两张原有的三单据(`sub_eval.pn_check` / `sub_eval.input_gap` /
> `sub_eval.verdict`)与三张外域表(`customer.profile` / `demand.request` / `parts.document`)
> 一并退役 —— **本活动这次重写不再声明、不再碰这五张表**;它们的存在性核对与录入不再是
> 这个活动的事(manifest.ts 头注原话)。老文档(见 git 历史)里那几张表的段落已随重写整篇撤下,
> 不是遗漏。
>
> 九张里有七张(除 `sub_eval.part` / `sub_eval.case` 两张本活动自持的工作台账外)的**物理结构**
> 由平台基线 `apps/api/src/app/seed-mvp-data.ts` 的 `MASTER_OBJECTS` 物化,声明合并对基线
> 基本是**纯 add-column**:活动只追加自己要用的列与标记(required / expect / tally / refersTo /
> requiredWhen / distinct / auto),不改基线已有列的语义。**唯一的例外是 `type_id` 这条身份链路**
> (`parts.chip_type.type_id` / `parts.type_param.type_id` / `parts.profile.chip_type_id` /
> `sub_eval.part.chip_type_id` / `sub_eval.case.chip_type_id` / `substitution.relation.chip_type_id`
> 六列同源同改)——声明把基线原本 `integer` 的这几列**加宽**成了 `text`,理由与 PG 类型转换
> 细节见 manifest.ts 该处的大注释与本文「怎么读这份文档」倒数第二条。那份声明同时是
> **授权面**:没声明的列写不进去,也不会出现在件上、判据里、数据域的活动分组里。
## 怎么读这份文档
- **★ = 重点字段**。每张表挑 5–8 个,在「重点字段」小节里**按重要程度从高到低**排;
数据结构表里在列名后打 ★。字段少于 5 个的表(字典、案卷)全部列入重点。
- **【人填】= 人在活动过程中亲手填、提交后入库的字段**(数据工单的件上编辑)。这类字段
**一律进重点**:它们是这一单里唯一不能靠配方补出来的事实。**S3 审核卡的四态/影响说明/
判断依据/UNKNOWN 去向不带这个标记**——那四格的初值是 AI 判出来的,人只是「审核时可以
就地改」,不是「唯一不能靠 AI 补出来的事实」;两者含义不同,详见各自表格的写入者说明。
- **这个活动的「写入者」词表与 `docs/数据域字段说明-E04-I01.md` 不同,单独在这里定义**——
E04/I01 全程多轮 agent 编排,W05 重写后**全程零 `guide`、AI 只在 S2 出场恰好一次**,
中间没有「模型每拍决定写什么」这件事,套用旧词表反而说不清楚:
- **人(件上)** —— 工作区那份 HTML 上点「编辑」改的格子,保存即落草稿层(与 E04/I01 同义)
- **AI** —— 整个活动**唯一一次**模型调用(S2 的 `analyze`)的出参,系统按 `lookup: result:items`
原样抄进台账;S3 人可在件上就地改
- **配方** —— `auto.do` 里 `project` 动作按声明(遍历 / `lookup` 现值 / `when` 链 / 字面量)
算出来的值,**不经模型**,每一拍都会重新求值一遍(幂等,值不变则不产生写入痕迹)
- **基座** —— 平台自己生成,不问人也不经模型(自增序号等)
- 没有「人(录入卡)」「AI(据人的意见改)」「承继」—— 本活动没有录入卡,也没有
「人写自由文本意见 → AI 读意见改具体行」这一步:S3 要改结论,人直接在件上改那一行;
T_release 全只读,打回重做要改的只有 `sub_eval.case`(且它也是配方算的,不是人填的)。
- 「约束」一栏的词表与 `docs/数据域字段说明-E04-I01.md`「怎么读这份文档」是**同一套**
(主键 / 自增 / 必填 / 建议填 / 取值域 / 引用 / 去重计数 / 脱敏),不重复抄一遍。
`requiredWhen` 类字段统一写「条件必填 · `<字段>=<值>` 时」。
- **`refersTo` 拒得很硬**:值必须在目标表里真实存在,查无此值 `draftWrite` 当场拒
(不是留到验收才发现)。装包时静态审核也会核 `refersTo` 目标地址本身是否存在。
### 一览
| 全景 | 台账地址 | 人读名 | 性质 | 本活动用到的列 | 人填列 |
|---|---|---|---|---|---|
| — | `parts.chip_type` | 芯片类型字典 | 主数据 · W05 唯一读写者 | 3 | 2(新建类型时,件上) |
| — | `parts.type_param` | 芯片类型核心参数字段 | 主数据 · W05 唯一读写者 | 5 | 5(新建类型 / 追加字段时,件上) |
| P-08 | `parts.profile` | 芯片主数据 | 主数据 · W05 唯一读写者 | 8 | 7(在库回填现值 · 缺库从零填,件上) |
| P-08 子 | `parts.parameter` | 标准化参数集 | 主数据 · W05 唯一读写者 | 5 | 2(值 · 说明,件上;主键两列虽挂 `data-col` 但由配方铺,人正常操作不改) |
| — | `sub_eval.part` | 本单两颗料(存在性与类型核对) | 本活动工作台账 | 6 | 0(配方每拍按入参与 `parts.profile` 现查重算,件上没有它的编辑入口) |
| — | `sub_eval.case` | 本单案卷 | 本活动工作台账 | 4 | 0(全部配方算) |
| P-11 | `substitution.relation` | 替代关系主记录 | 主数据 · W05 唯一读写者 | 13 | 0(配方按规则算,人不直接改) |
| P-11 子 | `substitution.difference_item` | 差异明细 | 主数据 · W05 唯一读写者 | 15 | 4(S3 就地改四态/影响说明/判断依据/UNKNOWN 去向;AI 先判,人可改) |
| P-06 | `validation.record` | 芯片验证记录 | 主数据 · W05 唯一读写者 | 7 | 0(配方按四态分布算) |
**四步**(全 `auto`,零 `guide`,AI 只在 S2 出场恰好一次):
`S1_inputs`(核对两颗料在不在库 / 是否归了同一芯片类型 / 参数是否齐,**缺什么才发一张
数据工单**——五条样例里「都在库·同型·齐备」那一条走的是零卡直通,不是特例)→
`S2_assess`(**整个活动唯一一次模型调用**:逐条核心参数判 MATCH/MISMATCH/UNKNOWN/CONDITIONAL
四态;推荐等级 / 风险 / 摘要 / 验证要求全部由配方按规则算,AI 不参与、不改一字)→
`S3_review`(结论审核,人工激活,件上就地改四态与三个自由文本格,可循环打回重做)→
`T_release`(落账放行,人工激活,全只读,签字后正式落账并发口 `assessment-released`,
可循环打回重做)。三道入场哨兵全读基座数(`sub_eval_part_*` / `substitution_*_rows` /
`_missing`),**案例模型里一面记账旗都没有**——「机检只读基座数」这条纪律在本仓被违反过
六次,W05 重写从设计上就不给它第七次机会。
---
## 一、`parts.chip_type` 芯片类型字典
按芯片类型(而不是按料号)标一次「这一类芯片要比哪几个核心参数、每个参数的说明与硬软
约束」——同一类型下两颗料从此**结构上**共用同一套参数名与约束档,不必再靠说明书措辞去
硬逼「两颗料对齐同一套参数名」(旧设计靠 `parts.parameter.param_name` 自指引用墙撑着,
测三次都没拦住,这次直接把墙的存在理由从结构上抹掉了)。件①型下拉里没有合适的,人在
`input-supplement` 的件②a 块现造一个;S1 配方选中之后**原样自投影一遍**(不论新建还是
挑既有类型都投,幂等、不产生多余写入),保证这一单的草稿层里**恒有这一行**——这既是
「保存即铺格」的前提,也是数据工单确认落地时不留悬空引用的前提(见 `docs/活动开发规范.md`
§4.6 关于 `gateLedgers` 的那条纪律)。
**重点字段(按重要程度)**
1. `type_id` ★ 主键 · 自增 —— 声明成 `text` 而不是 `integer`(见文档头注),写入时按
「现有最大 + 1」续号,不进表单、只读态摆「系统生成」
2. `type_name` ★【人填】必填 —— 新建类型时件上现填,已有类型从下拉挑
3. `description` ★【人填】建议填 —— 这个类型是什么(供选型参考)
**数据结构**
| 列名 | 人读名 | 类型 | 约束 | 写入者 | 说明 |
|---|---|---|---|---|---|
| `type_id` ★ | 类型编号 | text | 主键 · 自增 | 基座(续号)| 基线原为 `integer`,声明加宽为 `text`(见头注) |
| `type_name` ★ | 类型名称 | text | 必填 | **人(件上)** | 新建类型时填 |
| `description` ★ | 类型说明 | text | 建议填 | **人(件上)** | |
---
## 二、`parts.type_param` 芯片类型核心参数字段
一类芯片一套字段清单,复合主键 `type_id + param_name`。新建类型(或给已有类型追加字段)时,
人在 `input-supplement` 的件②b 块逐条定下字段名 / 说明 / 硬软约束 / 参考单位;S1 配方同样
把选中类型此刻的字段清单**原样自投影一遍**(与 `parts.chip_type` 同一条道理、同一次修正 ——
挑已有类型时这张表天然可能零行,不投一遍会被「零行 = 没干完」的闸误判)。**这张表是 S2
唯一一次 AI 调用的入参来源**:逐条参数连同 `param_desc`(为什么要紧)一起交给模型判四态。
**重点字段(按重要程度)**
1. `type_id` ★【人填,选择】主键之一 · 引用 `parts.chip_type.type_id`
2. `param_name` ★【人填】主键之一 —— 参数名
3. `param_desc` ★【人填】必填 —— **AI 分析时读的就是它**:参数名本身说不清「它为什么要紧」,
写成参数名的复述等于没写
4. `constraint_type` ★【人填,选择】必填 · 取值域 `hard`/`soft` —— 硬约束 = 不满足就不能用
(烧芯片 / 超温域 / 装不上);软约束 = 影响表现但可权衡(功耗 / 噪声)
5. `param_unit` ★【人填】建议填 —— 参考单位;**S1 铺参数骨架、S2 判定都直接抄它**
(不是回填每颗料自己记的单位)——同一类型下两颗料必须按同一个单位比,这是这次比对的基准
**数据结构**
| 列名 | 人读名 | 类型 | 约束 | 写入者 | 说明 |
|---|---|---|---|---|---|
| `type_id` ★ | 所属类型 | text | 主键之一 · 引用 `parts.chip_type.type_id` | **人(件上,下拉选)** | 基线原为 `integer`,声明加宽为 `text` |
| `param_name` ★ | 参数名 | text | 主键之一 | **人(件上)** | |
| `param_desc` ★ | 参数说明 | text | 必填 | **人(件上)** | AI 判定时读它 |
| `constraint_type` ★ | 约束类型 | text | 必填 · 取值域 | **人(件上,下拉选)** | 从 `parts.parameter` 搬到这里按类型标一次(旧设计每颗料各标一次,同一参数两颗料标法不一致就成了可能) |
| `param_unit` ★ | 参考单位 | text | 建议填 | **人(件上)** | `parts.parameter` 铺骨架 / S2 判定均直接抄它 |
---
## 三、`parts.profile` 芯片主数据(P-08)
一颗料一行。S1 配方按启动参数(`originalInternalPn` / `candidateInternalPn`)给两颗料各铺
一行骨架:**在库的那颗回填此刻的现值**(人不必重填,但看得到、能改),**要建的那颗全空**
等人填。`chip_type_id` 必填意味着「在库但还没归类型的老料」也会被拦住——那一格回填出来是
空的,人得先给它选一个类型。人不必在件上「加一行」:要建哪两颗料是启动参数定死的事,
行由配方摆好,他只填格子。
**重点字段(按重要程度)**
1. `internal_pn` ★ 主键 —— 全链路只认它;值随两颗料由启动参数定,件上虽挂 `data-col`
(行的钥匙)但人正常操作不改它
2. `chip_type_id` ★【人填,选择】必填 · 引用 `parts.chip_type.type_id` —— 两颗料**必须**
归到同一个类型,`sub_eval.part.chip_type_id` 的 `distinct` 判据钉着这一条
3. `brand_name` ★【人填】必填
4. `lifecycle_status` ★【人填,选择】必填 · 取值域 `active`/`nrnd`/`eol`/`discontinued`/`risk`
5. `manufacturer_name` ★【人填】建议填
6. `specification_description` ★【人填】建议填
7. `datasheet_ref` / `datasheet_version` ★【人填】建议填(成对)—— 证据回链
**数据结构**
| 列名 | 人读名 | 类型 | 约束 | 写入者 | 说明 |
|---|---|---|---|---|---|
| `internal_pn` ★ | 内部料号 | text | 主键 | 配方(按入参铺行)| 在库的沿用现值,不在库的等人填别的格子后自然成立 |
| `chip_type_id` ★ | 芯片类型 | text | 必填 · 引用 `parts.chip_type.type_id` | 配方(在库回填现值)/ **人(件上,下拉选)** | 基线原为 `integer`,声明加宽为 `text` |
| `brand_name` ★ | 品牌名称 | text | 必填 | 配方(在库回填现值)/ **人(件上)** | |
| `lifecycle_status` ★ | 生命周期状态 | text | 必填 · 取值域 | 配方(在库回填现值)/ **人(件上,下拉选)** | |
| `manufacturer_name` ★ | 制造商名称 | text | 建议填 | 配方(在库回填现值)/ **人(件上)** | |
| `specification_description` ★ | 规格描述 | text | 建议填 | 配方(在库回填现值)/ **人(件上)** | |
| `datasheet_ref` ★ | Datasheet 引用 | text | 建议填 | 配方(在库回填现值)/ **人(件上)** | |
| `datasheet_version` ★ | Datasheet 版本 | text | 建议填 | 配方(在库回填现值)/ **人(件上)** | |
> 基线物理表里还留着这次重写不再声明的旧列(`customer_pn` / `normalized_parameter_set` /
> `package` / `process_technology` / `eol_pcn_status` / `application_scenarios` /
> `pin_to_pin_relation_ref`)——那是**同一张表被本活动更早的旧九步版本用过**的痕迹
> (封装/工艺这次已经变成按类型标在字典里,不再是主记录上单独一列),不是别的活动在共享它;
> add-column 合并不删旧列,记录浏览里看到它们空着是正常的,判断一列还算不算数,以
> **当前这份声明**为准。
---
## 四、`parts.parameter` 标准化参数集(P-08 子表)
一颗料一个核心参数一行,复合主键 `internal_pn + param_name`。S1 配方按「这两颗料 ×
选中类型的字段清单」铺骨架(`prune:true`,换类型会把不再相关的陈旧参数行清掉):
`param_value`/`param_note` 若在库已有记录就回填现值,没有就留空等人填;`param_unit`
**永远**从 `parts.type_param` 字典直接复制,不回填每颗料自己记的单位、也不可编辑
(与块①/②b 不同,这一列连孪生输入框都不给)——同一类型下两颗料必须按同一个基准单位比,
S2 判定用的 `unit` 与件上摆的是同一处来源,不允许分叉。
**重点字段(按重要程度)**
1. `internal_pn` ★ 主键之一 · 引用 `parts.profile.internal_pn` —— 挂 `data-col` 是因为它是
行的钥匙,不是人正常操作会去改的格子
2. `param_name` ★ 主键之一 —— 同上,由配方按字典铺,不是人填的自由文本
3. `param_value` ★【人填】必填 —— 差异评估逐项比对的唯一输入;原厂规格书查不到就留空,
**留空会被红带点名,但编一个值比留空坏得多**(说明书明写的禁令)
4. `param_unit` ★ 建议填 —— **恒等于字典值**,配方每拍覆盖,手改也会在下一拍被盖回去
5. `param_note` ★【人填】建议填 —— 值的说明
**数据结构**
| 列名 | 人读名 | 类型 | 约束 | 写入者 | 说明 |
|---|---|---|---|---|---|
| `internal_pn` ★ | 所属料号 | text | 主键之一 · 引用 `parts.profile.internal_pn` | 配方(按字典 × 两颗料铺骨架) | 挂 `data-col` 只为行的钥匙 |
| `param_name` ★ | 参数名 | text | 主键之一 | 配方 | 同上 |
| `param_value` ★ | 值 | text | 必填 | 配方(在库回填现值)/ **人(件上)** | 查不到留空,不许估、不许拿别的参数的值顶替 |
| `param_unit` ★ | 单位 | text | 建议填 | 配方(恒抄 `parts.type_param` 字典) | 只读,连孪生输入框都不给 |
| `param_note` ★ | 说明 | text | 建议填 | 配方(在库回填现值)/ **人(件上)** | |
> 基线物理表里还留着一列这次重写不再声明的 `constraint_type`(旧设计每颗料各标一次约束,
> 这次已搬到 `parts.type_param` 按类型标一次)——同一条「add-column 不删旧列」的道理。
---
## 五、`sub_eval.part` 本单两颗料(存在性与类型核对,本活动工作台账)
S1 配方**每一拍都重算**的一张表(不是「写一次就不再动」):按启动参数铺两行(`role`
恰有 `original`/`candidate` 各一行),`found_pn_ref` 现查 `parts.profile` 是否已有这个
`internal_pn`、`exists_status` 据此判 `found`/`absent`、`chip_type_id` 现查在库现值。
**这张表没有任何人工编辑入口**——它是「要评估什么」那张件的**行来源**
(`data-rows="mine:sub_eval.part" data-edit="parts.profile"`),人在件上改的是
`parts.profile`,行会在下一拍被这张表原样重铺。
**重点字段(按重要程度)**
1. `exists_status` ★ 必填 · 取值域 `found`/`absent` —— 存在性核对的核心结论,S1 收步与
S2 入场哨兵都读它(`sub_eval_part_exists_status_found = 2` 才算「两颗都在库」)
2. `internal_pn` ★ 主键之一
3. `eval_ref` ★ 主键之一 —— 本单案号,`W05-` + 开案时刻 14 位数字,串联本活动自持的两张
工作台账
4. `role` ★ 必填 · 取值域 `original`/`candidate`
5. `chip_type_id` ★ 必填 · **去重计数**(`distinct`)—— 「两颗料是不是同一个类型」从此是
基座数得出来的数(`sub_eval_part_distinct_chip_type_id`),S1 收步与 S2 入场的硬门
6. `found_pn_ref` ★ **条件必填**(`exists_status=found` 时)· 引用 `parts.profile.internal_pn` ——
「在库」是一次 `lookup` 的结果,不是一句断言;判 `found` 就必须拿得出一个基座查得到
的料号
**数据结构**
| 列名 | 人读名 | 类型 | 约束 | 写入者 | 说明 |
|---|---|---|---|---|---|
| `eval_ref` ★ | 评估案号 | text | 主键之一 | 配方 | |
| `internal_pn` ★ | 内部料号 | text | 主键之一 | 配方(按启动参数铺) | |
| `role` ★ | 角色 | text | 必填 · 取值域 | 配方 | `original` 恰一行 / `candidate` 恰一行 |
| `found_pn_ref` ★ | 在库核对依据 | text | 条件必填 · `exists_status=found` · 引用 `parts.profile.internal_pn` | 配方(现查 `parts.profile`) | 查无此值 `exists_status` 判 `absent` |
| `exists_status` ★ | 在库状态 | text | 必填 · 取值域 `found`/`absent` | 配方 | 每拍现算,不设 `created` 档、不靠谁记账 |
| `chip_type_id` ★ | 芯片类型 | text | 必填 · 去重计数 | 配方(现查 `parts.profile`) | 基线原为 `integer`,声明加宽为 `text` |
---
## 六、`sub_eval.case` 本单案卷(本活动工作台账)
一单一行的案卷,挂着摆给人看的一句话(`gate_reason`)与收口摘要(`summary`)。**没有一面
记账旗**——完成与否全部由别处的基座读数(`sub_eval_part_*` / `substitution_*_rows` /
`_missing`)判定,这张表只是「说清此刻是什么状况」的一处落点。由两步接力写:S1 每拍
写 `eval_ref`/`chip_type_id`/`gate_reason`(供数据工单卡面的「还差什么」摆),T_release
再补一笔 `summary`(供落账确认页的结论卡摘要)。
**重点字段**
1. `eval_ref` ★ 主键 —— `W05-` + 开案时刻 14 位数字(与引擎注给模型铸主键用的
`case:openedAt` 同一函数,跨单唯一)
2. `gate_reason` ★ —— **摆在数据工单件上给人看的那句话**;`when` 链逐级判断此刻缺什么
(两颗料没填全 / 有料号查无记录 / 有料号没归类型 / 两颗料类型不一致 / 还有必填格空着),
都不缺时是空串。**这一笔排在铺骨架那两笔(`parts.profile`/`parts.parameter`)之后
不是随手写的次序**——它读的 `parts_profile_missing`/`parts_parameter_missing`
是骨架刚建出来的基座读数,排前面会读到刷新之前的旧数(详见 `docs/活动开发规范.md`
§4.6 新增纪律)
3. `chip_type_id` ★ —— 本单锁定的芯片类型(原料那一颗的),供 S2 组装入参时 `lookup`
4. `summary` ★ —— T_release 写的收口一句话(`<候选PN> 替代 <原PN>:<差异摘要>`),落账
确认页「结论卡」的摘要来源
**数据结构**
| 列名 | 人读名 | 类型 | 约束 | 写入者 | 说明 |
|---|---|---|---|---|---|
| `eval_ref` ★ | 评估案号 | text | 主键 | 配方(S1,按开案时刻铸) | |
| `chip_type_id` ★ | 本单锁定的芯片类型 | text | — | 配方(S1) | 供 S2 `lookup` 用 |
| `gate_reason` ★ | 入参未就绪原因(件上摆的就是它) | text | — | 配方(S1,`when` 链) | 都不缺时是空串;S1 发卡条件读的正是「非空」 |
| `summary` ★ | 结论摘要 | text | — | 配方(T_release) | `<候选PN> 替代 <原PN>:<差异摘要>` |
---
## 七、`substitution.relation` 替代关系主记录(P-11)
一单一行(旧设计是「一个未放弃的候选一条」,新设计**一次评估恰一对一候选**,不再有
「多候选、部分放弃」)。S2 与 S3 共用**同一份**投影(`RELATION_PROJECTION`):S2 首算,
人在 S3 就地改了差异明细的某条四态之后,同一份投影重算一遍——两处各写一份早晚分叉,
分叉的那一刻屏上会同时摆着两个互相矛盾的等级。**推荐等级 / 风险 / 摘要 / 理由 / 验证
要求全部由规则与模板拼,不让 AI 编**——于是不会出现「有硬约束不符却写着推荐」这种事;
人在 S3 改的是差异明细的四态,这一行跟着重算,人**不直接改**这张表本身。
**重点字段(按重要程度)**
1. `substitution_relation_id` ★ 主键 —— `SUB-<案号>`
2. `eval_ref` ★ 必填 —— **本活动新增列(基线没有)**,串联本单
3. `recommendation_level` ★ 必填 · 取值域 `recommended`/`conditional`/`not_recommended`
(旧设计还有 `alternate` 档,新设计的判定分类三分,这一档随之退役)—— 配方按 §3.5 的
规则算:有硬约束不符必 `not_recommended`,否则有 UNKNOWN/CONDITIONAL/软约束不符任一
即 `conditional`,全一致才 `recommended`
4. `original_internal_pn` / `candidate_internal_pn` ★ 必填 · 引用 `parts.profile.internal_pn`
5. `risk_level` ★ 必填 · 取值域 `low`/`medium`/`high` —— 随 `recommendation_level` 联动算
6. `verification_requirement` ★ **条件必填**(`recommendation_level=conditional` 时)
7. `rule_version` ★ 必填 —— 随结论落账,锁定 `R-2026.07`
8. `validation_id` ★ —— 关联验证ID(`VAL-<案号>`)
**数据结构**
| 列名 | 人读名 | 类型 | 约束 | 写入者 | 说明 |
|---|---|---|---|---|---|
| `substitution_relation_id` ★ | 替代关系ID | text | 主键 | 配方 | `SUB-<案号>` |
| `eval_ref` ★ | 评估案号 | text | 必填 | 配方 | 本活动新增列(基线没有) |
| `original_internal_pn` ★ | 原内部料号 | text | 必填 · 引用 `parts.profile.internal_pn` | 配方 | |
| `candidate_internal_pn` ★ | 候选内部料号 | text | 必填 · 引用 `parts.profile.internal_pn` | 配方 | |
| `chip_type_id` ★ | 芯片类型 | text | 必填 | 配方 | 基线原为 `integer`,声明加宽为 `text` |
| `recommendation_level` ★ | 推荐等级 | text | 必填 · 取值域 | 配方(按规则算) | 不让 AI 编 |
| `recommendation_reason` | 推荐理由 | text | 必填 | 配方(按规则拼句) | |
| `difference_summary` | 差异摘要 | text | 必填 | 配方(按规则拼句) | 「共 N 项核心参数:X 项一致 / … 」句式 |
| `risk_level` ★ | 替代风险等级 | text | 必填 · 取值域 | 配方(按规则算) | |
| `verification_requirement` ★ | 验证要求 | text | 条件必填 · `recommendation_level=conditional` | 配方 | |
| `validation_id` | 关联验证ID | text | — | 配方 | `VAL-<案号>` |
| `analysis_status` ★ | 分析状态 | text | 必填 | 配方 | 恒 `fae_confirmed`(基线其余档位本活动不写) |
| `rule_version` ★ | 差异规则版本 | text | 必填 | 配方 | 锁定 `R-2026.07` |
> **只读边界**:基线在这张表上还建了 `customer_approval_status` / `customer_approval_at` /
> `customer_approval_ref` / `customer_feedback` 四列,由**独立审批流**维护;本活动的声明
> 里没有这四列的写权(禁令 `no-customer-approval-write`)。基线物理表里还留着这次重写
> 不再声明的 `request_id` / `project_code` / `bom_position` / `substitution_type` /
> `analysis_report_ref` / `confidence_score`——那是旧九步版本用过的列,同一条
> 「add-column 不删旧列」的道理。
---
## 八、`substitution.difference_item` 差异明细(P-11 子表)
一颗料的一条核心参数一行,主键 `substitution_relation_id + difference_item_id`;行由
`parts.type_param` 字典 `each` 出来(`prune:true`)——**字段清单由字典给,AI 的出参只是
拿 `param_name` 去 `lookup` 的一张查表**,它漏一条,那一行的四态就是空,`required` 立刻
变成 `_missing > 0`,提交闸与放行门当场拦住(不会像「AI 出参驱动行」那样漏两条就少两行、
每一行看着都合规)。**S3 是这张表唯一的人工编辑入口**:四态 / 影响说明 / 判断依据 /
UNKNOWN 去向可就地改,其余(参数项 / 说明 / 约束 / 原值 / 候选值 / 单位)只读。
**重点字段(按重要程度)**
1. `substitution_relation_id` ★ 主键之一 · **去重计数** · 引用 `substitution.relation.substitution_relation_id` ——
「几条关系已经有明细」就是数它的不同值(恰应为 1)
2. `difference_item_id` ★ 主键之一
3. `difference_status` ★ 必填 · 取值域 `match`/`mismatch`/`unknown`/`conditional` ——
**S2 那一次 AI 判定的核心出参**
4. `judgement_class` ★ 必填 · 取值域 `match`/`soft_mismatch`/`hard_mismatch`/`unknown`/`conditional` ——
**合成列**(先认 `difference_status`,`mismatch` 再按 `constraint_type` 分硬/软):
「硬约束不符有几条」不是任何单列 `tally` 数得出来的数,`recommendation_level` 的判据
全靠这一列
5. `impact_statement` ★ 必填 —— MATCH 写「无」,其余写「换上去会不会比现在差、差在哪儿」
(不复述参数值)
6. `evidence_note` ★ 必填 —— 判断依据
7. `unknown_disposition` ★ **条件必填**(`difference_status=unknown` 时)—— UNKNOWN 的去向
8. `constraint_type` ★ 必填 · 取值域 `hard`/`soft` —— 承自 `parts.type_param` 字典这一行本身
**数据结构**
| 列名 | 人读名 | 类型 | 约束 | 写入者 | 说明 |
|---|---|---|---|---|---|
| `substitution_relation_id` ★ | 替代关系ID | text | 主键之一 · 去重计数 · 引用 `substitution.relation.substitution_relation_id` | 配方 | 隐藏在件上(`.rowkey`),仅供 `resolveRowKey` 凑主键 |
| `difference_item_id` ★ | 差异明细ID | text | 主键之一 | 配方 | `<关系id>-<参数名>` |
| `parameter_name` | 标准参数名 | text | 必填 | 配方(抄字典) | |
| `parameter_desc` | 参数说明 | text | 建议填 | 配方(抄字典) | 归档件不必回查字典,快照一份 |
| `constraint_type` | 约束类型 | text | 必填 · 取值域 | 配方(抄字典) | |
| `original_value` | 原物料参数值 | text | 必填 | 配方(`lookup` `parts.parameter`) | |
| `candidate_value` | 候选物料参数值 | text | 必填 | 配方(`lookup` `parts.parameter`) | |
| `parameter_unit` | 参数单位 | text | 建议填 | 配方(抄字典) | |
| `difference_status` ★ | 差异状态 | text | 必填 · 取值域 | **AI**(S2 `analyze` 出参) | |
| `judgement_class` ★ | 判定分类 | text | 必填 · 取值域 | 配方(合成列) | 由 `difference_status` × `constraint_type` 算 |
| `impact_statement` ★ | 影响说明(MATCH 写「无」) | text | 必填 | **AI** / **人(件上,S3 可改)** | |
| `evidence_note` ★ | 判断依据 | text | 必填 | **AI** / **人(件上,S3 可改)** | 基线里这一列旧名是 `evidence_ref`,本活动改用新名 `evidence_note`(不写 P-10 了,AI 能给的是依据不是文件引用) |
| `unknown_disposition` ★ | UNKNOWN 去向(验证任务号 / 补来源动作) | text | 条件必填 · `difference_status=unknown` | **AI** / **人(件上,S3 可改)** | |
| `risk_level` | 风险等级 | text | 必填 · 取值域 | 配方 | `hard_mismatch`→high,`match`→low,其余 medium |
| `rule_version` | 差异规则版本 | text | 必填 | 配方 | 锁定 `R-2026.07` |
> 基线物理表里还留着两列这次重写不再声明的旧列:`evidence_ref`(已被 `evidence_note`
> 取代,不是同一列改名,是新声明了一列、旧列作废不写)与 `confidence`(AI 置信度,旧设计
> 的工具出参字段,新设计没有这个概念)。
---
## 九、`validation.record` 芯片验证记录(P-06)
事实类表:本活动只在 S2/S3 给每单挂**一行固定的**「验证要求」占位
(`validation_status` 恒 `not_started`,`validation_type` 恒 `parametric`)——它不是实测
结果,**不许虚构实测**;真正测完、写测试结果是另一件事(下游或线下流程的事)。**一单固定
一条**,即使全部参数都 MATCH、没有待验项,也要写「本轮无待验项」而不是不写这一行
(验收判据 `validation-landed` 钉着 `validation_record_rows = 1`)。
**重点字段(按重要程度)**
1. `validation_id` ★ 主键 —— `VAL-<案号>`
2. `eval_ref` ★ 必填 —— **本活动新增列(基线没有)**
3. `validation_internal_pn` ★ 必填 · 引用 `parts.profile.internal_pn` —— 恒为候选料号
4. `validation_status` ★ 必填 —— 恒 `not_started`
5. `next_validation_action` ★ 必填 —— 要测什么、什么条件;没有待验项写「本轮无待验项」
6. `validation_type` ★ 必填 · 取值域(基线六档,本活动只写 `parametric`)
7. `test_condition` ★ 建议填 —— 有待验项才写测试条件,否则「无」
**数据结构**
| 列名 | 人读名 | 类型 | 约束 | 写入者 | 说明 |
|---|---|---|---|---|---|
| `validation_id` ★ | 验证ID | text | 主键 | 配方 | `VAL-<案号>` |
| `eval_ref` ★ | 评估案号 | text | 必填 | 配方 | 本活动新增列(基线没有) |
| `validation_internal_pn` ★ | 验证内部料号 | text | 必填 · 引用 `parts.profile.internal_pn` | 配方 | 恒为候选料号 |
| `validation_type` ★ | 验证类型 | text | 必填 · 取值域 | 配方 | 恒 `parametric`(本活动只挂这一种) |
| `validation_status` ★ | 验证状态 | text | 必填 | 配方 | 恒 `not_started` |
| `test_condition` | 测试条件 | text | 建议填 | 配方 | 有 UNKNOWN/CONDITIONAL 才写,否则「无」 |
| `next_validation_action` ★ | 后续验证动作 | text | 必填 | 配方 | 没有待验项写「本轮无待验项」 |
> 基线物理表里还留着这次重写不再声明的一批旧列(`request_id` / `customer_code` /
> `test_parameters` / `test_result` / `issue_risk` / `sample_version` / `sample_lot` /
> `evidence_ref`)——那批列是「真验证发生后才有的事实」(测试参数、测试结果、样品批次),
> 本活动只写「验证要求」占位阶段,不写实测结果,同一条「add-column 不删旧列」的道理。
---
## 十、人填字段总表(按人办事的那一屏)
| 活动 · 屏 | 卡的形式 | 落进哪张表 | 人亲手填的列 |
|---|---|---|---|
| `S1_inputs` · 数据工单「差异评估的输入还差几格」(条件发卡,零卡直通是常态路径之一) | 执行工单(零输入卡,件上编辑 + 增删行) | `parts.chip_type`(新建类型时)/ `parts.type_param`(新建类型 / 追加字段时)/ `parts.profile` / `parts.parameter` | chip_type:`type_name`/`description`;type_param:`type_id`(选)/`param_name`/`param_desc`/`constraint_type`(选)/`param_unit`;profile:`chip_type_id`(选)/`brand_name`/`lifecycle_status`(选)/`manufacturer_name`/`specification_description`/`datasheet_ref`/`datasheet_version`;parameter:`param_value`/`param_note` |
| `S3_review` · 差异评估结论审核(可循环打回重做) | 决策卡(`decide`,`variant:'review'`)+ 件上就地改 | `substitution.difference_item` | `difference_status`(选)/`impact_statement`/`evidence_note`/`unknown_disposition`(AI 先判,人核对后可改) |
| `T_release` · 落账放行(可循环打回重做) | 终局验收工单(签字,人工验收清单五条) | 无(全只读) | 无 —— 人只签字或打回重做。**这一步本身不提供改内容的手段**(`writes` 只声明了 `sub_eval.case`,且这张表没有人工编辑入口、全由配方算);要改结论内容,退回 `S3_review` 的件上直接改 |
---
## 十一、在数据域里看这些表时的几条提醒
1. **`sub_eval.part` / `sub_eval.case` 是本活动自持的工作台账**(domain `sub_eval`,与 E04
`crm_coverage.*`、I01 `content_brand.*` 同一类地位),其余七张是共享主数据的物理表 ——
但截至这次重写,**只有 W05 声明并读写它们**(全仓 grep `domain: 'parts'` /
`'substitution'` / `'validation'` 唯一命中处就是这个包),称「共享」容易让人以为另有
活动在用,这份文档统一改称「W05 唯一读写者」。
2. **`type_id` 这条身份链路是本仓唯一一处「integer 加宽成 text」的声明**(六列同源:
`parts.chip_type.type_id` / `parts.type_param.type_id` / `parts.profile.chip_type_id` /
`sub_eval.part.chip_type_id` / `sub_eval.case.chip_type_id` /
`substitution.relation.chip_type_id`)。原因是平台的自增续号器只认「整数型主键走一条路、
字符串型主键走另一条路」,而「`auto:true` 又是 `integer` 的主键」这个组合会撞上两处
平台级坑(`resolveRowKey` 抢跑续号 / `coerceToColumn` 按 `Number()` 解析掉前导零导致
`refersTo` 比对失配)——声明层加宽成 `text` 绕开,不是修复那两处平台代码(它们仍带着
坑,见 `docs/debt.md`)。字段说明详见 `parts.chip_type.type_id` 那一行与
manifest.ts 对应大注释。
3. **基线物理表里普遍还留着旧九步版本的列**(见各表小节末尾的提醒框)——`add-column`
合并不删旧列,记录浏览里看到它们空着是正常的;判断一列还算不算数,**以活动清单当前
这份声明为准**,不以物理表结构为准。
4. **`substitution.relation.customer_approval_status` 等四列是只读边界,不是本活动没做完**——
由**独立审批流**维护,本活动的声明里没有写权(禁令 `no-customer-approval-write`)。
5. **试跑单默认也落真库**:与 E04 / I01 同一个开关(`sys.task.land_master`,测试台起单
默认打开)——跑一遍活动,新建的芯片类型 / 料号 / 参数都会真的进正式表;要它不落,
起单时显式关掉(脚本化 e2e 要求自己传 `false`)。
6. **本活动全程无 agent 多轮编排,零技能、零工具**——`contract.resources.tools` 与
`declarations.tools` 都是 `[]`,`assets.skills` 也是 `[]`(旧两枚工具、两张 DMN、三条
技能戳已随重写整体退役)。全部行为写在 `PlanStep.auto` 声明式配方里,详见
`docs/活动开发规范.md` §4.6。
---
*字段口径以 `activities/part-substitution-assessment/src/manifest.ts` 的
`declarations.ledger` 为准;七张共享主数据的基线物理结构见
`apps/api/src/app/seed-mvp-data.ts` 的 `MASTER_OBJECTS`;四步剧本与配方见
`activities/part-substitution-assessment/src/case-model.ts` 的 `ASSESSMENT_STEPS` 与
`NARRATIVE`;确定性步骤(`auto` 五动词)的通用机制见 `docs/adr/ADR-014-deterministic-steps.md`
与 `docs/活动开发规范.md` §4.6;业务设计源见 `docs/SS02-ACT-WORK-05-html/`
(活动流程与工单说明)、`docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md`
与《SS02-SS03-SS04_Demo台账字段细化与统一方案》。*