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/activities/part-substitution-assessment/src/manifest.ts
T
xcsandClaude Fable 5 51914292dc feat(w05): 数据工单第四轮九连改 —— 笔↔软盘卡角编辑、只选不建、浅蓝题头、镜像修数字
2026-08-31 用户一次点齐九件,逐条落:
① 两行编辑闸退役 → 卡右上角笔(开当前这一面的编辑)↔ 软盘(保存)+ 叉(放弃);
  按钮就是平台的 data-edit-open/save/cancel 触发器,快照/取消机制原样,按面显隐全在样式表。
② 保存一瞬间下拉变数字 → ui-kit mirrorTwins:下拉镜像**选项文案**不镜 value,并把
  selected 属性拨到人选的那一项(守卫 CSS 与取消回显都认它)。2 测。
③ 参数输入框占位提示 → 渲染器 twinInputs 认 data-ph 落成 placeholder(平台小扩面,1 测);
  值格「规格书上的值 · 查不到留空」,说明格「补充说明 · 选填」。
④ 参数说明空着不再摆「建议」→ 什么都不摆(连兜底破折号一起收)。
⑤ 区序对调:芯片档案在前,比对基准在后。
⑥ **新建芯片类型整条路移除**(只能选已有类型):抽屉与两张编辑表(种子行/Critical-1
  修法/thead 回归网)退役,S1 配方 ②③ 自投影删除(八笔→六笔),gateLedgers 收窄到
  profile+parameter,writes 同步,gate_reason 与指导书改口「只选不建」;样例改名
  「候选缺库·建档定型」;e2e 案例 0 改走选既有 type_1(type_id=90 那套绕坑法随路退役)。
  退役面的七条钉测按仓例 it.skip 留墓志铭(钉的是已移除的路径,不是放松断言)。
⑦ 题头回浅蓝渐变(原目标件那条 120°),深色夜景只留验收两件。
⑧ 对话区执行卡不再摆「参考信息」板块(basis 数据照旧随卡走,详情/归档仍有)。
⑨ 比对基准降噪:字段收成丸(名+硬软徽+单位),说明藏进悬浮气泡指上即看;
  类型说明一行截断、悬浮展开。

测:ui-kit 33(mirror 2 新)+ 渲染器 61(ph 1 新)+ 包内 104(38 steps 重排)+ 2525 全仓
四门绿;2.10.0 装库 SQL 六探针全中(hd-light/软盘/data-ph/零表格/无 chip_type 编辑面/
gateLedgers 两张);:8787 已重启载入 data-ph 渲染器。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 16:41:14 +08:00

580 lines
40 KiB
TypeScript
Raw 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 差异评估(案例活动)。
*
* **2026-08-29 整体重写(@2.0.0)**:从「九步、两枚工具、全程 agent 驱动」改成「四步、零工具、
* 零 `guide`、AI 出场恰好一次」。设计权威:
* `docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md`(下称「spec」)。
* 用户原话立的场:「不是把所有东西都丢给 ai 来处理…一定是系统先确定性的从数据库中取出数据后交给
* ai 分析」——`docs/ACT-WORK-05-端到端测试记录.md` 的 A–AE 发现表里,八条事故都落在
* 「本来就该是确定的」那几步(编号抄别单、判在库判错、数据工单派给在库的料…),
* 真正需要判断的只有一件事:**这一条参数,候选跟原厂比算不算事**。
*
* **2026-08-30 更新**:这份注释原写于重写 Task 2(只动 `declarations.ledger` 与
* `contract` 两段那一刻),彼时 `case-model.ts` 还是旧九步——**Task 3 已把它整体重写成
* spec §4 的四步**(`S1_inputs` / `S2_assess` / `S3_review` / `T_release`)+ 四道哨兵 +
* 零 `guide`(行为改由 `PlanStep.auto` 声明式配方接管),清单与案例模型此刻同版一致,
* 不再有「一边新一边旧」的过渡态。`assets` 里当时已不自洽的那几处(spec §8 的退役清单,
* 见「计划裁定 G3」)同批处理完毕。
*
* ## 九张台账声明(spec §3)
* 两张新字典(`parts.chip_type` / `parts.type_param`,Task 1 已物化 + 播种)承接「约束类型按类型
* 标一次」;P-08 主记录 `parts.profile` 撤 `createOnly`(在库老料要能补上 `chip_type_id` 这一格)、
* 封装与工艺退休(它们本来就是「核心参数」,该以类型字段的身份出现在字典里);P-08 子表
* `parts.parameter` 撤 `constraint_type` 与参数名自指引用(名字对不对由 S1 配方铺格子这件事本身
* 保证,不必再靠一堵测三次都没拦住的墙)。原来的三张单据(`pn_check` / `input_gap` / `verdict`)
* 由两张顶掉:`sub_eval.part`(**一单恰两行** —— 配方 `each` 遍历「哪几颗料要建」的唯一数据源,
* 拆成两行的理由见 spec §3.3「为什么非拆成两张不可」)与 `sub_eval.case`(一单一行的案卷,
* 挂入参未就绪的原因与结论摘要)。P-11 明细加合成列 `judgement_class`(约束类型 × 四态的合成,
* 因为「硬约束不符有几条」不是任何单列 `tally` 数得出来的数)。P-01 需求主记录 / P-10 原厂资料 /
* 客户档案**本活动不碰**(它们的存在性核对与录入不再是这个活动的事)。
*
* ## 契约(spec §4.1/§4.5/§7)
* 启动只剩 `originalInternalPn` / `candidateInternalPn` 两格(单数,一次评估恰一对一候选),
* 都选填 —— 缺了由第一步的配方派数据工单补。零工具:`contract.resources.tools` 与
* `declarations.tools` 都是 `[]`,这是这次重写的立场,不是疏漏(四态改 S2 唯一一次 `analyze`
* 判定,推荐等级改配方按 spec §3.5 的规则算)。判据全部改读基座数(`域_对象_rows` / `_missing` /
* `_distinct_列` / tally 计数),没有一条读活动自己写的汇总列。
*/
import type { ActivityManifest } from '@contracts';
import { activityIdSchema } from '@contracts';
import { CMMN_XML, NARRATIVE, ASSESSMENT_PHASES, ASSESSMENT_STEPS } from './case-model.ts';
import { LEDGER_BOARD, LEDGER_BOARD_DATA } from './views/ledger-board.ts';
import { INPUT_SUPPLEMENT, DIFFERENCE_REVIEW, LANDING_CONFIRM } from './views/report.ts';
import { ACTIVITY_ID_TEXT, D } from './constants.ts';
export { D, RULE_VERSION } from './constants.ts';
const ACTIVITY_ID = activityIdSchema.parse(ACTIVITY_ID_TEXT);
/** 字段声明的短写(与 E04 同一套写法:显式 primaryKey / masked,其余按需) */
type F = { name: string; type: 'text' | 'number' | 'integer' | 'boolean' | 'date' | 'timestamp' | 'json'; label: string; primaryKey?: boolean; required?: boolean; expect?: boolean; tally?: string[]; auto?: boolean; distinct?: boolean; refersTo?: string; requiredWhen?: { column: string; value: string } };
const col = (f: F) => ({ primaryKey: false, masked: false, ...f });
export const manifest: ActivityManifest = {
identity: {
activityId: ACTIVITY_ID,
name: 'ACT-WORK-05 · 差异评估',
/* 重写不是改 —— 同版重装是 no-op(静默丢失,踩过) */
packageVersion: '2.10.0',
category: '需求分析与方案匹配',
sdkVersion: '0.1.0-m5',
checksum: 'dev-checksum-part-substitution-assessment',
},
/* ── 契约七件 ────────────────────────────────────────────────── */
contract: {
activityId: ACTIVITY_ID,
version: '1.0.0',
input: {
/*
* **两格,都必填**(2026-08-30 修 Critical 2,推翻 spec §4.1 原来的「都选填」)——
* 一次评估恰是「一对一候选」,这两颗料号是这件事的**身份**,不是可以事后补的细节:
* 库里有就选,没有就自己写一个新的(合法的新料号字符串,不是留空)。
* 「启动后由第一步的配方核对存在性、缺了就派数据工单补齐」这句话说的是料号的**主数据**
* (品牌/类型/参数),从来不是料号**本身**——旧措辞把两件事含糊成了一件事。
*
* 都选填时留空的下场(评审逐行核过的链条):S1 配方 `project into sub_eval.part` 拿
* `originalInternalPn`/`candidateInternalPn` 铸两行的主键 `internal_pn`,两个空字符串
* 是**同一个主键**——第二笔 `draftWrite` 覆盖第一笔,`sub_eval.part` 收成**一行**不是
* 两行;`gate_reason` 的 `sub_eval_part_rows < 2` 当场为真,提示「在下面把两格补上」——
* 而块① 的行源是 `mine:sub_eval.part`(人不加行,没有「+加一行」按钮),那两格根本
* 不存在。`doneWhen` 永不成立,提交也按不下去,**唯一出路是终止重开**,且五条样例与
* e2e 五案例都没填过空值,两条验证线都照不到这条死路。
*
* 改必填**不是关上「留空」这扇门就完事**——平台早有配套机制接住它(2026-07-30 立的
* 「受理归一」:缺必填不拒单,任务落成 `awaiting-human`,一张补全卡问负责人要那几个
* 字段,答完合并进 `data` 才转 `running`;`apps/api/src/modules/task/internal/usecase/
* task-service.ts` 的 `missingRequired`/`askCompletion`,`__tests__/intake.test.ts`
* 已经把这条路测得很实)。必填不是墙,是把「两颗料号是什么」这件唯一要人先说清楚的
* 事挪到起单那一刻问清楚,不留给配方去猜一个猜不出来的空字符串。
*
* 运营端启动表单渲染这两格为「选择 + 新增」下拉(`x-control: combo`),候选从
* `parts.profile.internal_pn` 现读(`x-options`);`x-order` 定表单次序(清单存在
* jsonb 里,书写顺序到不了运营端)。requestDescription / candidateInternalPns(数组)/
* datasheets 三键已随重写撤下 —— 这一版没有「需求主记录」「N 候选并行」「预填
* datasheet」这几件事。
*/
data: {
type: 'object',
required: ['originalInternalPn', 'candidateInternalPn'],
properties: {
originalInternalPn: {
type: 'string',
title: '原内部料号',
description: '差异评估基准料号(必填);库里有就选,没有就自己写一个新的 —— 它的主数据(品牌/类型/参数)缺了才由第一步派数据工单补齐。',
examples: ['LM2596S-5.0'],
'x-control': 'combo',
'x-options': { ledger: 'parts.profile', value: 'internal_pn', label: 'internal_pn' },
'x-order': 1,
},
candidateInternalPn: {
type: 'string',
title: '候选内部料号',
description: '被评估的候选料号(必填);库里有就选,没有就自己写一个新的 —— 它的主数据(品牌/类型/参数)缺了才由第一步派数据工单补齐。',
examples: ['XL2596S-5.0'],
'x-control': 'combo',
'x-options': { ledger: 'parts.profile', value: 'internal_pn', label: 'internal_pn' },
'x-order': 2,
},
},
},
messageAllowed: true,
messageHint: 'FAE 可在此说明客户的应用边界或验证条件',
},
output: {
/* 单一必发口:签下去 P-11 落账、P-06 落账;载荷是已落地的事实,不是待办 */
mandatory: [
{
name: 'assessment-released',
description: '差异评估结论已落账(P-11 替代关系主记录 + 差异明细、P-06 验证记录)—— 载荷即已落地的事实',
payload: {
type: 'object',
required: ['evalRef', 'relationId', 'originalPn', 'candidatePn', 'recommendationLevel', 'matchTotal', 'mismatchTotal', 'unknownTotal', 'conditionalTotal', 'validationId'],
properties: {
evalRef: { type: 'string', title: '评估案号' },
relationId: { type: 'string', title: '替代关系ID' },
originalPn: { type: 'string', title: '原内部料号' },
candidatePn: { type: 'string', title: '候选内部料号' },
recommendationLevel: { type: 'string', title: '推荐等级' },
matchTotal: { type: 'integer', title: 'MATCH 条数' },
mismatchTotal: { type: 'integer', title: 'MISMATCH 条数' },
unknownTotal: { type: 'integer', title: 'UNKNOWN 条数' },
conditionalTotal: { type: 'integer', title: 'CONDITIONAL 条数' },
validationId: { type: 'string', title: '关联验证ID' },
},
},
},
],
selective: [],
},
/* 一份成果常不止一张表:差异评估结论由明细 + 关系两张共同承载,落账内容由关系 + 验证记录承载 */
requiredDeliverables: [
{ key: 'difference-items', label: '差异评估结论', ledger: 'substitution.difference_item', ledgers: ['substitution.difference_item', 'substitution.relation'], editableAtRelease: [] },
{ key: 'landing', label: '落账内容', ledger: 'substitution.relation', ledgers: ['substitution.relation', 'validation.record'], editableAtRelease: [] },
],
/*
* 验收判据(spec §7)—— 五条机检 L1/L2 全读基座数,两条 L4 裁量,一条 L5 责任关(不上卡)。
* `input-ready` 的谓词与 S1(Task 3 待写)的 `doneWhen` 同构:两颗都在库、类型一致、参数不缺 ——
* **不写「参数行数 = 字段数×2」**,那不是基座数得出来的数(`parts_type_param_rows` 数的是
* 本单写下的字段行,不是整本字典;给判据编一个数不出来的数,是这个仓踩过六次的同一个坑)。
*/
acceptanceCriteria: [
{
key: 'input-ready',
label: '评估输入已就绪:原 PN 与候选都在库、归了同一个芯片类型、主数据与参数值都不缺必填',
level: 'L1',
predicate:
'sub_eval_part_exists_status_found = 2 and sub_eval_part_missing = 0 and sub_eval_part_distinct_chip_type_id = 1 and ' +
'parts_profile_missing = 0 and parts_parameter_missing = 0',
},
{
key: 'every-param-judged',
label: '每条核心参数都有明确状态:每条差异明细都是 MATCH / MISMATCH / UNKNOWN / CONDITIONAL 之一,没有一条缺必填',
level: 'L2',
predicate:
'substitution_difference_item_missing = 0 and ' +
'substitution_difference_item_difference_status_match + substitution_difference_item_difference_status_mismatch + ' +
'substitution_difference_item_difference_status_unknown + substitution_difference_item_difference_status_conditional = substitution_difference_item_rows',
},
{
key: 'level-by-rule',
/* §3.5 的规则原样译成析取式:推荐等级只有一种取值能配上当前的 judgement_class 分布,规则说了算不是话术说了算 */
label: '推荐等级与判定分类一致:有硬约束不符的候选不能是「推荐」,等级由规则算,不由措辞改判',
level: 'L2',
predicate:
'(substitution_relation_recommendation_level_not_recommended = 1 and substitution_difference_item_judgement_class_hard_mismatch > 0) or ' +
'(substitution_relation_recommendation_level_conditional = 1 and substitution_difference_item_judgement_class_hard_mismatch = 0 and ' +
'(substitution_difference_item_judgement_class_unknown > 0 or substitution_difference_item_judgement_class_conditional > 0 or substitution_difference_item_judgement_class_soft_mismatch > 0)) or ' +
'(substitution_relation_recommendation_level_recommended = 1 and substitution_difference_item_judgement_class_hard_mismatch = 0 and ' +
'substitution_difference_item_judgement_class_unknown = 0 and substitution_difference_item_judgement_class_conditional = 0 and substitution_difference_item_judgement_class_soft_mismatch = 0)',
},
{
key: 'unknown-has-disposition',
label: '每个 UNKNOWN 都有去向:去向、影响说明、判断依据没有一条缺',
level: 'L2',
predicate: 'substitution_difference_item_missing = 0',
},
{
key: 'validation-landed',
label: '验证记录已落账:P-06 恰有一行且不缺必填(全推荐也要写「本轮无待验项」)',
level: 'L2',
predicate: 'validation_record_rows = 1 and validation_record_missing = 0',
},
/*
* **两条 L4 补了谓词**(2026-08-30,真跑 e2e 撞见的结构性发现 —— **不是**记进
* `docs/ACT-WORK-05-端到端测试记录.md`:那份文档整篇是 2026-08-27 及更早的旧
* 九步设计,四步重写[2026-08-29]之后没有再往里追加过任何一条,已在文档开头标为
* 历史;这条发现连同其余 2026-08-30 的都只活在这份源码注释与 `docs/debt.md` 里)。
* 「裁量项带谓词时谓词说了算」这条纪律
* (2026-08-02 立,`engines.ts` 的 `criterionCheck`:`settledByData`)本仓已有先例,
* 从前 W05 这两条 L4 是裸的(无 `predicate`),而 T_release 全程 `auto` 驱动、
* `submit` 是五个声明式动词之一(`packages/contracts/src/auto-recipe.ts`),
* **没有第六个 `judge` 动词** —— 裁量项没谓词就只能等 `judgement`(Agent 的 `judge`
* 行动)来结算,而这个活动压根不会有 Agent 走到这一步。结果是 `submittable()`
* 对这两条永远读到 `pass:false`,`T_release` 的 `submit` 每一拍都被
* `raiseAcceptance` 拒,而拒绝不算错误(不触发 `failedAt`)、下一拍原样重来 ——
* 稳定地把步数预算耗到 `exhausted`,而不是报错崩溃,所以静态审计与包内自测都没照见它。
*
* 两条谓词只覆盖**客观、基座算得出来的那一半**(有没有值、值哪来的),
* **主观那一半(写得好不好、可信不可信)已经由人工验收清单接住**——S3 审核卡
* 第 1/4 条「逐条对过原料与候选料的参数值…」「影响说明站得住…」正是这两条 L4
* 标签里「站得住」「对得上」那两个字的着落点,人签字通过那一刻已经确认过。
* 谓词为真只说「客观前提具备」,不越权替人下「写得好」这个主观判断。
*/
{
key: 'evidence-credible',
label: '证据链可信:每条差异的影响说明与判断依据站得住',
level: 'L4',
/* 客观前提:每一行都有值(必填不缺)——写得「站不站得住」是 S3 人工验收清单的事 */
predicate: 'substitution_difference_item_missing = 0',
},
{
key: 'values-traceable',
label: '参数值与原厂资料对得上:候选与原 PN 的取值来源可追溯',
level: 'L4',
/* 客观前提:原始参数集不缺、差异明细里逐条的原值候选值也不缺 —— 两值均来自
parts.parameter 的 lookup,不是模型现编;「对不对得上」是 S3 人工验收清单的事 */
predicate: 'parts_parameter_missing = 0 and substitution_difference_item_missing = 0',
},
/* L5 是责任关:那张卡本身就是这道关,卡面上不摆它(基座统一滤) */
{ key: 'gate03-release', label: 'ACT-GATE-03 评审放行:签下去 P-11 落账、P-06 落账,结论供下游消费', level: 'L5' },
],
revisableAtRelease: [],
prohibitions: [
{ key: 'no-fabricated-parameter', statement: 'UNKNOWN 是一等公民:查不到的参数值必须显式写「未披露」并记去向,不得伪造、不得用估计值或空串冒充' },
{ key: 'no-override-hard-mismatch', statement: '硬约束 MISMATCH 不得被任何措辞改判 MATCH;要改只能补证据后重比' },
{ key: 'no-customer-approval-write', statement: '客户批准状态由独立审批流维护,本活动不写 customer_approval_status(没有这一列的写权)' },
{ key: 'no-commercial-judgement', statement: '本活动只答技术可替代性:不做数量、价格、交期、采购判断' },
],
authorization: {
discretion: [],
preauthorizedActions: [],
/* 调人三次(补数据 / 审核 / 放行);零工具、AI 只出场一次 —— 预算随之从 16/90 步收紧到 8/40 步 */
humanCallBudget: 8,
},
resources: {
tools: [],
skills: [],
models: ['deepseek-v4-flash'],
roles: ['FAE/RI'],
budget: { steps: 40, tokens: 300_000, activeMinutes: 60, humanCalls: 8 },
},
},
/* ── 声明 ────────────────────────────────────────────────────── */
declarations: {
models: ['deepseek-v4-flash'],
/* 零工具 —— 这是这次重写的立场,不是疏漏(见头注) */
tools: [],
ledger: [
/* ── 两张类型字典(parts 域,与 P-08 同域;Task 1 已物化 + 播种)── */
{
domain: 'parts',
object: 'chip_type',
kind: 'master',
label: '芯片类型字典',
fields: [
/*
* **`type_id` 声明成 `text` 而不是 `integer`**(Task 7b,2026-08-29,件① 补「新建类型」
* 逼出来的两处平台级发现,记在这里免得下一个人重踩):
* ①`resolveRowKey`(`apps/api/src/app/routes/desk.ts`)的「③ 续号」只认 `isSeq`
* (declared type 是 integer/number),不看 `auto` —— 这个类型碰巧是**唯一**一个
* `auto:true` 又是 `integer` 的主键,于是它抢在 `writeEditedRows` 的 `autoKeys()`
* (真正的续号器,数official+草稿叠加、不会撞号)前面,自己按「本单草稿有几行」
* 铸了个从 1 起的号——种子已经占了 `type_id=1/2`,人第一次新建类型就会撞车,
* 把种子的「DC-DC 降压稳压器」悄悄改名。②即便①不发生,`autoKeys()` 的续号是
* 两位补零字符串(`'03'`),而**引用它的那几列**(`parts.profile.chip_type_id` 等)
* 若声明成 `integer`,写入时会被 `coerceToColumn` 按 `Number()` 解析掉前导零——
* `'03'` 存成 `parts.chip_type.type_id`、`3` 存成 `parts.profile.chip_type_id`,
* `refersTo`/`data-by` 那几处按 `String(...)` 比对的查找会全部失配(`'03' !== '3'`),
* 写入当场被 `refersTo` 拒、或下拉/查表显示不出人刚建的类型。
* **两处病根都是「integer 类型的自增列」这个此前从未出现过的组合触发的**——本仓
* 唯一先例 `contact_code` 一直是 `text`。**`resolveRowKey`/`coerceToColumn` 这两处
* 平台代码本身仍带着这两个坑没修**(下一个建同样组合主键的人会重新踩上,已记
* `docs/debt.md`)——这里只是在活动自己的声明层绕开,不是修复。
*
* 改成 `text` 为什么是安全的(此列与下面 5 处同引用列一起改,保持同一条身份链路
* 全程一种类型):**`ledger.__draft`(任务级草稿层)才是 JSONB**
* (`ledger-store.ts` 的 `DRAFT_TABLE`/`DRAFT_DDL`:`key`/`patch`/`baseline` 三列都是
* `jsonb`)——但 `master`/`record` 这类**正式表是真实的物理列**(同文件 `PG_TYPE`
* 映射,`integer → bigint`,`create table`/`add column`/`alter column ... type ...`
* 都拼的是真 PG 类型)。改声明安全,靠的是 `schema-merge.ts` 的**单调加宽 lattice**
* (`SCALAR_CHAIN.integer = ['integer', 'number', 'text']`,`widen()`
* 判定 `integer → text` 是**合法加宽**、不是类型冲突)配合 `widen-column` 那支生成的
* 显式转换(`alter table … alter column … type text using col::text`——整数转文本
* 沿 PG 的 `bigint::text` 转型,没有失败路径)。这次重装(`activities:install`)会
* 把 `ledger.parts__chip_type.type_id` 等六列从 `bigint` 真的 `ALTER … TYPE text`
* 过去一遍。
*
* S-FEEL 那几处只做等值/去重比较,不做数值运算,不受影响(已逐处核对:distinct
* 计数 / lookup·take / refersTo 皆按字符串比,`>` 只用在 `distinct_chip_type_id`
* 这个计数列上,不用在值本身)。查证过程详见 Task 7b 报告
* (`.superpowers/sdd/2026-08-29-act-work-05-rewrite/task-7b-report.md`)。
*/
col({ name: 'type_id', type: 'text', label: '类型编号', primaryKey: true, auto: true }),
col({ name: 'type_name', type: 'text', label: '类型名称', required: true }),
col({ name: 'description', type: 'text', label: '类型说明', expect: true }),
],
},
{
domain: 'parts',
object: 'type_param',
kind: 'master',
label: '芯片类型核心参数字段',
fields: [
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text——见上面那条大注释 */
col({ name: 'type_id', type: 'text', label: '所属类型', primaryKey: true, refersTo: 'parts.chip_type.type_id' }),
col({ name: 'param_name', type: 'text', label: '参数名', primaryKey: true }),
/* AI 分析时读它 —— 这一格说清「这个参数是什么意思」,是 S2 唯一一次 analyze 出场的入参之一 */
col({ name: 'param_desc', type: 'text', label: '参数说明', required: true }),
/* 约束类型按类型标一次,长在这里(不在每颗料的 `parts.parameter` 上)—— 见该表的头注 */
col({ name: 'constraint_type', type: 'text', label: '约束类型', required: true, tally: ['hard', 'soft'] }),
col({ name: 'param_unit', type: 'text', label: '参考单位', expect: true }),
],
},
/*
* P-08 芯片主数据 —— 声明后为八列,物理表其余列本活动不碰。`createOnly` 撤掉
* (spec §3.2):它当初是为了拦模型凭空建料,而配方现在按本单两颗料铺行,
* 凭空建在结构上已不可能;撤掉它换来一件必需的事 —— 给在库的老料补上
* `chip_type_id` 这一格(迁移前建的行没有类型,不许改就永远补不了)。
* package / process_technology / eol_pcn_status / application_scenarios 不声明:
* 封装和工艺本来就是「核心参数」,该以类型字段的身份出现在字典里,不在主记录上另开一列。
*/
{
domain: 'parts',
object: 'profile',
kind: 'master',
label: '芯片主数据(P-08)',
fields: [
col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true }),
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text —— 见该字段的大注释 */
col({ name: 'chip_type_id', type: 'text', label: '芯片类型', required: true, refersTo: 'parts.chip_type.type_id' }),
col({ name: 'brand_name', type: 'text', label: '品牌名称', required: true }),
col({ name: 'lifecycle_status', type: 'text', label: '生命周期状态', required: true, tally: ['active', 'nrnd', 'eol', 'discontinued', 'risk'] }),
col({ name: 'specification_description', type: 'text', label: '规格描述', expect: true }),
col({ name: 'manufacturer_name', type: 'text', label: '制造商名称', expect: true }),
col({ name: 'datasheet_ref', type: 'text', label: 'Datasheet 引用', expect: true }),
col({ name: 'datasheet_version', type: 'text', label: 'Datasheet 版本', expect: true }),
],
},
/*
* P-08 子表:标准化参数集,一行一参数。`createOnly` 撤掉(理由同上一张:在库那颗料的
* 现值要能回填进草稿层,只许建新就写不进去)。两处退休(spec §3.2):
* `constraint_type` 搬去 `parts.type_param`(同一个参数在两颗料上标法不一致这件事从结构上
* 消失);`param_name` 的自指引用墙与 `distinct` 一并退休 —— 它当初是为了硬逼两颗料用同一套
* 参数名,而字段清单现在由类型字典给、行由配方按字典铺,名字对不上根本不可能发生。
*/
{
domain: 'parts',
object: 'parameter',
kind: 'master',
label: '标准化参数集(P-08 子表)',
fields: [
col({ name: 'internal_pn', type: 'text', label: '所属料号', primaryKey: true, refersTo: 'parts.profile.internal_pn' }),
col({ name: 'param_name', type: 'text', label: '参数名', primaryKey: true }),
col({ name: 'param_value', type: 'text', label: '值', required: true }),
col({ name: 'param_unit', type: 'text', label: '单位', expect: true }),
/* 值说明,AI 也读它(spec §3.2) */
col({ name: 'param_note', type: 'text', label: '说明', expect: true }),
/*
* **约束类型不在这里** —— 它长在 `parts.type_param`(按类型标一次)。
* 从前每颗料各标一次,同一个参数在两颗料上标法不一致就成了可能;
* 而比对是逐参数对着比的,两边约束档不同,判出来的四态就没有意义。
*/
],
},
/*
* ── 活动自持两张(spec §3.3,顶掉退休的 pn_check / input_gap / verdict 三张)──
* `sub_eval.part` 是配方唯一能拿来遍历「哪几颗料要建」的数据源(`each: 'mine:sub_eval.part'
* where exists_status = absent`)—— 挤进一行案卷里就没法 `where` 过滤,而不能过滤就会给
* 已经在库的那颗料也铺一遍空参数行,这是非拆成两张不可的理由。
*/
{
domain: D,
object: 'part',
kind: 'record',
caseKey: 'eval_ref',
label: '本单两颗料(存在性与类型核对)',
fields: [
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true }),
col({ name: 'role', type: 'text', label: '角色', required: true, tally: ['original', 'candidate'] }),
/*
* **「在库」是一次 lookup 的结果,不是一句断言**(spec §3.3):判 found 就必须拿得出
* 一个基座查得到的料号,`refersTo` 让查无此值当场拒。`exists_status` 与这一列**每一拍
* 由配方重算**(不设 created 档、不靠谁记账)—— 人把料建好之后自己变 found。
*/
col({ name: 'found_pn_ref', type: 'text', label: '在库核对依据', refersTo: 'parts.profile.internal_pn', requiredWhen: { column: 'exists_status', value: 'found' } }),
col({ name: 'exists_status', type: 'text', label: '在库状态', required: true, tally: ['found', 'absent'] }),
col({
name: 'chip_type_id',
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text —— 见该字段的大注释 */
type: 'text',
label: '芯片类型',
required: true,
/*
* **`distinct`**:「两颗料是不是同一个类型」从此是基座数得出来的数
* (`sub_eval_part_distinct_chip_type_id`)—— 不同取值恰好 1 种才算一致。
* 这一条是 S1 收步与 S2 入场的硬门:两边字段清单不是同一套,逐项比对无从谈起。
*/
distinct: true,
}),
],
},
{
domain: D,
object: 'case',
kind: 'record',
caseKey: 'eval_ref',
label: '本单案卷',
fields: [
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text —— 见该字段的大注释 */
col({ name: 'chip_type_id', type: 'text', label: '本单锁定的芯片类型' }),
col({ name: 'gate_reason', type: 'text', label: '入参未就绪原因(件上摆的就是它)' }),
col({ name: 'summary', type: 'text', label: '结论摘要' }),
],
},
/*
* P-11 替代关系主记录 —— 一单一行。退休三列(spec §3.4):`substitution_type`(新流程里没有
* 它的定夺人)、`confidence_score`、`analysis_report_ref`;`recommendation_level` 的
* `alternate` 档一并退休(原 D02 的 configurable 来路已不存在,只剩三档)。
*/
{
domain: 'substitution',
object: 'relation',
kind: 'master',
label: '替代关系主记录(P-11)',
fields: [
col({ name: 'substitution_relation_id', type: 'text', label: '替代关系ID', primaryKey: true }),
col({ name: 'eval_ref', type: 'text', label: '评估案号', required: true }),
col({ name: 'original_internal_pn', type: 'text', label: '原内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
col({ name: 'candidate_internal_pn', type: 'text', label: '候选内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text —— 见该字段的大注释 */
col({ name: 'chip_type_id', type: 'text', label: '芯片类型', required: true }),
col({ name: 'recommendation_level', type: 'text', label: '推荐等级', required: true, tally: ['recommended', 'conditional', 'not_recommended'] }),
col({ name: 'recommendation_reason', type: 'text', label: '推荐理由', required: true }),
col({ name: 'difference_summary', type: 'text', label: '差异摘要', required: true }),
col({ name: 'risk_level', type: 'text', label: '替代风险等级', required: true, tally: ['low', 'medium', 'high'] }),
col({ name: 'verification_requirement', type: 'text', label: '验证要求', requiredWhen: { column: 'recommendation_level', value: 'conditional' } }),
col({ name: 'validation_id', type: 'text', label: '关联验证ID' }),
/* 固定 fae_confirmed —— 分析状态与客户批准状态分离,本活动只写这一层 */
col({ name: 'analysis_status', type: 'text', label: '分析状态', required: true }),
col({ name: 'rule_version', type: 'text', label: '差异规则版本', required: true }),
],
},
/*
* P-11 差异明细子表 —— 一参数一行。`evidence_ref` 改名 `evidence_note`「判断依据」:
* 不写 P-10 了,AI 能给的就是依据不是文件引用,名字得说实话(spec §3.4)。
*/
{
domain: 'substitution',
object: 'difference_item',
kind: 'master',
label: '差异明细(P-11 子表)',
fields: [
col({ name: 'substitution_relation_id', type: 'text', label: '替代关系ID', primaryKey: true, distinct: true, refersTo: 'substitution.relation.substitution_relation_id' }),
col({ name: 'difference_item_id', type: 'text', label: '差异明细ID', primaryKey: true }),
col({ name: 'parameter_name', type: 'text', label: '标准参数名', required: true }),
/* 字段说明快照,归档件不必 join 回字典 */
col({ name: 'parameter_desc', type: 'text', label: '参数说明', expect: true }),
col({ name: 'constraint_type', type: 'text', label: '约束类型', required: true, tally: ['hard', 'soft'] }),
col({ name: 'original_value', type: 'text', label: '原物料参数值', required: true }),
col({ name: 'candidate_value', type: 'text', label: '候选物料参数值', required: true }),
col({ name: 'parameter_unit', type: 'text', label: '参数单位', expect: true }),
/* AI 判:逐参数比对,S2 唯一一次 analyze 出场的出参之一 */
col({ name: 'difference_status', type: 'text', label: '差异状态', required: true, tally: ['match', 'mismatch', 'unknown', 'conditional'] }),
/*
* **`judgement_class` 是合成列**:推荐等级要数的是「**硬约束**不符有几条」,
* 而基座读数只会数单列 —— 把「约束类型 × 四态」先合成一列,规则才算得出来。
* 由配方按 §3.5 的 when 链算,不是 AI 出的。
*/
col({ name: 'judgement_class', type: 'text', label: '判定分类', required: true, tally: ['match', 'soft_mismatch', 'hard_mismatch', 'unknown', 'conditional'] }),
col({ name: 'impact_statement', type: 'text', label: '影响说明(MATCH 写「无」)', required: true }),
col({ name: 'evidence_note', type: 'text', label: '判断依据', required: true }),
col({ name: 'unknown_disposition', type: 'text', label: 'UNKNOWN 去向(验证任务号 / 补来源动作)', requiredWhen: { column: 'difference_status', value: 'unknown' } }),
col({ name: 'risk_level', type: 'text', label: '风险等级', required: true, tally: ['low', 'medium', 'high'] }),
col({ name: 'rule_version', type: 'text', label: '差异规则版本', required: true }),
],
},
/* P-06 验证记录:一单一行,关联的是本单案号(P-01 不在本活动里,顶掉 request_id) */
{
domain: 'validation',
object: 'record',
kind: 'master',
label: '芯片验证记录(P-06)',
fields: [
col({ name: 'validation_id', type: 'text', label: '验证ID', primaryKey: true }),
col({ name: 'eval_ref', type: 'text', label: '评估案号', required: true }),
col({ name: 'validation_internal_pn', type: 'text', label: '验证内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
col({ name: 'validation_type', type: 'text', label: '验证类型', required: true, tally: ['pin_to_pin', 'functional', 'reliability', 'certification', 'retest', 'parametric'] }),
/* 固定 not_started */
col({ name: 'validation_status', type: 'text', label: '验证状态', required: true }),
col({ name: 'test_condition', type: 'text', label: '测试条件', expect: true }),
col({ name: 'next_validation_action', type: 'text', label: '后续验证动作', required: true }),
],
},
],
listeners: [],
harness: {
memoryWindow: 24,
stallAfter: 'PT4H',
nudge: [{ after: 'PT2H', action: 'nudge' }],
retry: { times: 2, backoff: 'PT30S' },
},
},
/* ── 资产初始版 ──────────────────────────────────────────────── */
assets: {
/*
* DMN 判定表已退役:D01(逐参数四态)改由 S2 唯一一次 analyze 判定;D02(推荐等级)的规则
* 搬进配方的 when 链(spec §3.5)—— 规则只许有一处真源,再留一份 DMN 就是同一件事两处
* 各存一份。`steps` 已是四步、`guide` 全部清空(auto 配方接管行为,Task 3 完成)。
*/
businessModel: { cmmnXml: CMMN_XML, narrative: NARRATIVE, dmn: { drd: [], tables: [] }, steps: ASSESSMENT_STEPS, phases: ASSESSMENT_PHASES },
/* 三条技能已随重写退役(全程无 agent,没有临场编排可言);skills.ts 已在 Task 10 删除 */
skills: [],
/*
* 五张旧局面全部作废(它们绑的九步与旧台账地址已经不存在)。**Task 7 已补回第一张**:
* `input-supplement`(评估输入补全,S1 的数据工单,三块竖排 —— 存在性与主数据 /
* 芯片类型字段 / 参数值)。**Task 8 已补回第二张**:`difference-review`(差异评估结论审核,
* S3 的验收工单 —— 对决头 + 四态分布带 + 推荐等级大徽章只读,逐条明细可就地改四态 /
* 影响说明 / 判断依据 / UNKNOWN 去向,收口四格只读)。**Task 9 已补回第三张**:
* `landing-confirm`(落账确认,T_release 的终局验收工单 —— 全只读,答「这一签下去发生
* 什么」:进哪几张表各几行 / 结论卡 / 四态分布并把硬约束不符逐条点名 / 待验项 / 发哪一口)。
* 三张件此刻齐了。看板自己的 `data` 声明(`LEDGER_BOARD_DATA`)同批换成了新九张表 ——
* `sub_eval.pn_check`/`sub_eval.verdict`/`demand.request`/`customer.profile`/
* `parts.document` 这五张已退役地址不再出现。
*/
viewTemplates: [
{ name: 'activity-ledger', html: LEDGER_BOARD, data: LEDGER_BOARD_DATA },
{ name: 'input-supplement', html: INPUT_SUPPLEMENT },
{ name: 'difference-review', html: DIFFERENCE_REVIEW },
{ name: 'landing-confirm', html: LANDING_CONFIRM },
],
cardSchemas: {},
/* 蒸馏钩子撤空:它是给 agent 在终态回看轨迹用的,而这个活动全程无 agent(AI 只出场一次、答一份结构化判断,没有可供归纳的多轮轨迹) */
distillHooks: [],
},
/*
* ── 样例集:五条形状真不同(常备案例即验收案例)──
* **2026-08-29 Task 11 换成新入参形状**(单数 `candidateInternalPn`,与 §4.1 的启动两参同步 ——
* 旧九步遗留的 `customerCode`/`requestDescription`/`candidateInternalPns`(数组)三键随之撤下,
* 起单表单本就只剩两格)。五条覆盖 S1 的五种入场局面:①候选缺库(新建类型那条路,S1 数据工单
* 三块都要动)②两颗都在库且同型(零卡直接进 S2)③两颗都在库但分属不同芯片类型(硬拦,
* 光凭其它格子填满绕不过去)④原 PN 缺库(候选在库)⑤两颗都缺库。
* 库里播了 LM2596S-5.0 / LM2596S-ADJ(DC-DC 降压稳压器,type_id=1)与 AMS1117-3.3
* (LDO 线性稳压器,type_id=2)三颗(见 seed-mvp-data.ts);XL2596S-5.0 / CN-DCDC-32V /
* LM2596S-3.3 刻意不在库。
*/
samples: [
{ name: '候选缺库·建档定型', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'XL2596S-5.0' }, expect: { ports: ['assessment-released'] } },
{ name: '都在库·零卡直分析', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'LM2596S-ADJ' }, expect: { ports: ['assessment-released'] } },
{ name: '类型不一致·硬拦', input: { originalInternalPn: 'LM2596S-5.0', candidateInternalPn: 'AMS1117-3.3' }, expect: { ports: ['assessment-released'] } },
{ name: '原PN缺库', input: { originalInternalPn: 'LM2596S-3.3', candidateInternalPn: 'LM2596S-ADJ' }, expect: { ports: ['assessment-released'] } },
{ name: '两颗都缺库', input: { originalInternalPn: 'CN-DCDC-32V', candidateInternalPn: 'XL2596S-5.0' }, expect: { ports: ['assessment-released'] } },
],
entry: { file: 'index.js', checksum: 'dev-entry-part-substitution-assessment' },
};