Archived
feat(w05): 清单重写 —— 九张台账、两个启动参数、零工具
台账:两张类型字典 + P-08 主/参数(撤 createOnly、封装工艺归字典)+ 案卷两张 + P-11 主/明细 + P-06。 契约:启动只剩原 PN 与候选 PN 两格且都选填(缺了由第一步派工单补);tools 清空 —— 这是立场不是疏漏。 判据全读基座数;明细加合成列 judgement_class,因为「硬约束不符有几条」不是单列 tally 数得出来的。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { activityManifestSchema } from '@contracts';
|
import { activityManifestSchema } from '@contracts';
|
||||||
import { createMockHost } from '@sdk';
|
import { createMockHost } from '@sdk';
|
||||||
import { manifest, D, RULE_VERSION } from '../src/manifest.ts';
|
import { manifest, D } from '../src/manifest.ts';
|
||||||
import activity from '../src/index.ts';
|
import activity from '../src/index.ts';
|
||||||
|
|
||||||
const ITEMS = ['S0_bind', 'S0d_p08_master', 'S0d_p10_document', 'S1_input_confirm', 'S2_normalize', 'S3_assess', 'S4_review', 'S5_prepare_landing', 'T_release'];
|
const ITEMS = ['S0_bind', 'S0d_p08_master', 'S0d_p10_document', 'S1_input_confirm', 'S2_normalize', 'S3_assess', 'S4_review', 'S5_prepare_landing', 'T_release'];
|
||||||
@@ -23,6 +23,9 @@ const VIEWS = ['p08-master-create', 'p10-document-create', 'eval-input-list', 'd
|
|||||||
const BOARD_VIEW = 'activity-ledger';
|
const BOARD_VIEW = 'activity-ledger';
|
||||||
const byAddress = (domain: string, object: string) => manifest.declarations.ledger.find((l) => l.domain === domain && l.object === object);
|
const byAddress = (domain: string, object: string) => manifest.declarations.ledger.find((l) => l.domain === domain && l.object === object);
|
||||||
const viewHtml = (name: string) => manifest.assets.viewTemplates.find((t) => t.name === name)?.html ?? '';
|
const viewHtml = (name: string) => manifest.assets.viewTemplates.find((t) => t.name === name)?.html ?? '';
|
||||||
|
/* 「台账声明」组用的短写(2026-08-29 重写引入):地址是 `域.对象` 一整串,`byAddress` 拆两段 */
|
||||||
|
const ledgerOf = (address: string) => manifest.declarations.ledger.find((l) => `${l.domain}.${l.object}` === address);
|
||||||
|
const colOf = (address: string, name: string) => ledgerOf(address)?.fields.find((f) => f.name === name);
|
||||||
|
|
||||||
describe('清单本身合规', () => {
|
describe('清单本身合规', () => {
|
||||||
it('过 @contracts 的 schema', () => {
|
it('过 @contracts 的 schema', () => {
|
||||||
@@ -33,12 +36,13 @@ describe('清单本身合规', () => {
|
|||||||
expect(manifest.declarations.listeners).toEqual([]);
|
expect(manifest.declarations.listeners).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('L1/L2 判据都给了谓词;L4 三条不给(裁量项);L5 恰一条', () => {
|
/* 2026-08-29 重写:spec §7 的判据表从九条改成八条(五条机检 L1/L2 + 两条 L4 裁量 + 一条 L5),L4 从三条收到两条 —— 证据链与硬约束两条留 L4,「推荐等级与明细一致」已升级成 L2 机检(level-by-rule) */
|
||||||
|
it('L1/L2 判据都给了谓词;L4 两条不给(裁量项);L5 恰一条', () => {
|
||||||
for (const c of manifest.contract.acceptanceCriteria) {
|
for (const c of manifest.contract.acceptanceCriteria) {
|
||||||
if (c.level === 'L1' || c.level === 'L2') expect(c.predicate, c.key).toBeDefined();
|
if (c.level === 'L1' || c.level === 'L2') expect(c.predicate, c.key).toBeDefined();
|
||||||
if (c.level === 'L4') expect(c.predicate, c.key).toBeUndefined();
|
if (c.level === 'L4') expect(c.predicate, c.key).toBeUndefined();
|
||||||
}
|
}
|
||||||
expect(manifest.contract.acceptanceCriteria.filter((c) => c.level === 'L4')).toHaveLength(3);
|
expect(manifest.contract.acceptanceCriteria.filter((c) => c.level === 'L4')).toHaveLength(2);
|
||||||
expect(manifest.contract.acceptanceCriteria.filter((c) => c.level === 'L5')).toHaveLength(1);
|
expect(manifest.contract.acceptanceCriteria.filter((c) => c.level === 'L5')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,21 +53,31 @@ describe('清单本身合规', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('三件必交付物都有台账地址;人环预算严格大于「基线 8 + 两轮审核循环 4」', () => {
|
/*
|
||||||
expect(manifest.contract.requiredDeliverables).toHaveLength(3);
|
* 2026-08-29 重写:必交付物从三件(评估输入清单 / 差异评估结论 / 拟落账内容)收到两件 ——
|
||||||
|
* 「评估输入清单」那件连同它绑的三张旧单据(pn_check/input_gap/verdict)一起退休。
|
||||||
|
* 人环预算从 16(候选放弃/创建决策等岔路)收到 8(补数据 / 审核 / 放行三次调人,零工具、
|
||||||
|
* AI 只出场一次)—— 见「契约 > 预算」那条把 8 钉死;这里只需两个数上下一致。
|
||||||
|
*/
|
||||||
|
it('两件必交付物都有台账地址;人环预算恰为 8', () => {
|
||||||
|
expect(manifest.contract.requiredDeliverables).toHaveLength(2);
|
||||||
for (const d of manifest.contract.requiredDeliverables) expect(d.ledger, d.key).toBeDefined();
|
for (const d of manifest.contract.requiredDeliverables) expect(d.ledger, d.key).toBeDefined();
|
||||||
expect(manifest.contract.authorization.humanCallBudget).toBeGreaterThan(12);
|
expect(manifest.contract.authorization.humanCallBudget).toBe(8);
|
||||||
expect(manifest.contract.resources.budget.humanCalls).toBeGreaterThan(12);
|
expect(manifest.contract.resources.budget.humanCalls).toBe(8);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('起单一格必填都不设(2026-08-28 用户定);客户两格同日从启动表单撤下;datasheet 是选填文件', () => {
|
/*
|
||||||
|
* 2026-08-29 重写:启动表单从「需求 + 原PN + 候选数组 + datasheet」四件收到「原PN + 候选」两件
|
||||||
|
* (单数,一次评估恰一对一候选)—— `requestDescription` / `candidateInternalPns`(数组)/
|
||||||
|
* `datasheets` 三键随 P-01 需求主记录一起撤下(本活动不再碰它)。逐条断言见「契约 > 启动两参」。
|
||||||
|
*/
|
||||||
|
it('起单一格必填都不设(2026-08-28 用户定);客户与需求两类字段已随重写从启动表单撤下', () => {
|
||||||
expect(manifest.contract.input.data['required']).toEqual([]);
|
expect(manifest.contract.input.data['required']).toEqual([]);
|
||||||
const launchProps = Object.keys(manifest.contract.input.data['properties'] as Record<string, unknown>);
|
const launchProps = Object.keys(manifest.contract.input.data['properties'] as Record<string, unknown>);
|
||||||
expect(launchProps).not.toContain('customerCode');
|
expect(launchProps).not.toContain('customerCode');
|
||||||
expect(launchProps).not.toContain('customerName');
|
expect(launchProps).not.toContain('customerName');
|
||||||
const properties = manifest.contract.input.data['properties'] as Record<string, { type?: string; format?: string }>;
|
expect(launchProps).not.toContain('requestDescription');
|
||||||
expect(properties['candidateInternalPns']?.type).toBe('array');
|
expect(launchProps).not.toContain('datasheets');
|
||||||
expect(properties['datasheets']?.format).toBe('file');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('五条样例:第一条满载走数据工单那条路,名字 ≤14 字,形状各不相同', () => {
|
it('五条样例:第一条满载走数据工单那条路,名字 ≤14 字,形状各不相同', () => {
|
||||||
@@ -76,7 +90,13 @@ describe('清单本身合规', () => {
|
|||||||
expect(newCustomer).toBeDefined();
|
expect(newCustomer).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('五张工单视图 + 一张运营侧看板都声明了,剧本每一步引用的 view 都在其中', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2):`assets.viewTemplates` 已按 spec §8 暂时只留 `activity-ledger`
|
||||||
|
* 一张(五张旧局面全部作废,它们绑的是已退休的九步与旧台账地址)—— Task 7–9 会各自加回
|
||||||
|
* `input-supplement` / `difference-review` / `landing-confirm` 三张新件,那时再补一条按新
|
||||||
|
* 视图集合钉死的断言。这条转性检查在过渡期间的意义不大,先跳过、不删。
|
||||||
|
*/
|
||||||
|
it.skip('五张工单视图 + 一张运营侧看板都声明了,剧本每一步引用的 view 都在其中', () => {
|
||||||
expect(new Set(manifest.assets.viewTemplates.map((v) => v.name))).toEqual(new Set([...VIEWS, BOARD_VIEW]));
|
expect(new Set(manifest.assets.viewTemplates.map((v) => v.name))).toEqual(new Set([...VIEWS, BOARD_VIEW]));
|
||||||
for (const step of manifest.assets.businessModel.steps as ReadonlyArray<{ view?: string }>) {
|
for (const step of manifest.assets.businessModel.steps as ReadonlyArray<{ view?: string }>) {
|
||||||
if (step.view !== undefined) expect(VIEWS, step.view).toContain(step.view);
|
if (step.view !== undefined) expect(VIEWS, step.view).toContain(step.view);
|
||||||
@@ -84,79 +104,120 @@ describe('清单本身合规', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('台账声明:授权面与《统一方案》口径', () => {
|
describe('台账声明', () => {
|
||||||
it('P-08 主键 internal_pn,标准化参数集拆成独立子表 parts.parameter(2026-08-27 结构改:与目标件一致)', () => {
|
it('恰好九条声明(七个业务对象,P-08 与 P-11 各是主表 + 子表)', () => {
|
||||||
const p08 = byAddress('parts', 'profile');
|
expect(manifest.declarations.ledger.map((l) => `${l.domain}.${l.object}`).sort()).toEqual([
|
||||||
expect(p08?.fields.filter((f) => f.primaryKey).map((f) => f.name)).toEqual(['internal_pn']);
|
'parts.chip_type', 'parts.parameter', 'parts.profile', 'parts.type_param',
|
||||||
expect(p08?.fields.find((f) => f.name === 'normalized_parameter_set')).toBeUndefined();
|
'sub_eval.case', 'sub_eval.part',
|
||||||
const param = byAddress('parts', 'parameter');
|
'substitution.difference_item', 'substitution.relation', 'validation.record',
|
||||||
expect(param?.kind).toBe('master');
|
].sort());
|
||||||
expect(param?.fields.filter((f) => f.primaryKey).map((f) => f.name)).toEqual(['internal_pn', 'param_name']);
|
|
||||||
expect(param?.fields.find((f) => f.name === 'internal_pn')?.refersTo).toBe('parts.profile.internal_pn');
|
|
||||||
/*
|
|
||||||
* 参数名自指引用:只能用台账里已经在用的那几个 —— 2026-08-27 实测三次,
|
|
||||||
* 模型给新料写英文 key,剧本写过两遍 + 一条乘法对账都拦不住「只建一颗」那一路。
|
|
||||||
*/
|
|
||||||
const pn = param?.fields.find((f) => f.name === 'param_name');
|
|
||||||
expect(pn?.refersTo).toBe('parts.parameter.param_name');
|
|
||||||
expect(pn?.distinct).toBe(true);
|
|
||||||
expect(param?.fields.find((f) => f.name === 'param_value')?.required).toBe(true);
|
|
||||||
expect(param?.fields.find((f) => f.name === 'constraint_type')?.tally).toEqual(['hard', 'soft']);
|
|
||||||
expect(param?.fields.find((f) => f.name === 'constraint_type')?.required).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('P-11 主记录的原 PN / 候选 PN 都是引用墙(查无此料当场拒 —— 存在性检查的硬门)', () => {
|
it('类型字典:type_id 是自增主键 —— 人在件上新建类型时留空,由基座续号', () => {
|
||||||
const rel = byAddress('substitution', 'relation');
|
expect(colOf('parts.chip_type', 'type_id')).toMatchObject({ primaryKey: true, type: 'integer', auto: true });
|
||||||
for (const name of ['original_internal_pn', 'candidate_internal_pn']) {
|
expect(colOf('parts.chip_type', 'type_name')?.required).toBe(true);
|
||||||
expect(rel?.fields.find((f) => f.name === name)?.refersTo, name).toBe('parts.profile.internal_pn');
|
});
|
||||||
|
|
||||||
|
it('字段字典:复合主键 + 约束类型长在这里(不在每颗料上)', () => {
|
||||||
|
expect(ledgerOf('parts.type_param')?.fields.filter((f) => f.primaryKey).map((f) => f.name)).toEqual(['type_id', 'param_name']);
|
||||||
|
expect(colOf('parts.type_param', 'type_id')?.refersTo).toBe('parts.chip_type.type_id');
|
||||||
|
expect(colOf('parts.type_param', 'param_desc')?.required).toBe(true);
|
||||||
|
expect(colOf('parts.type_param', 'constraint_type')).toMatchObject({ required: true, tally: ['hard', 'soft'] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('P-08:类型必填并引用字典;createOnly 撤掉 —— 在库老料要能补上类型这一格', () => {
|
||||||
|
expect(colOf('parts.profile', 'chip_type_id')).toMatchObject({ required: true, type: 'integer', refersTo: 'parts.chip_type.type_id' });
|
||||||
|
expect(ledgerOf('parts.profile')?.createOnly).toBeUndefined();
|
||||||
|
/* 封装与工艺本来就是「核心参数」—— 它们该以类型字段的身份出现在字典里,不在主记录上另开一列 */
|
||||||
|
for (const gone of ['package', 'process_technology', 'eol_pcn_status', 'application_scenarios']) {
|
||||||
|
expect(colOf('parts.profile', gone)).toBeUndefined();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('客户批准状态**不在声明里**:本活动没有它的写权(只读边界)', () => {
|
it('参数子表:约束类型与自指引用墙都退休了', () => {
|
||||||
const rel = byAddress('substitution', 'relation');
|
expect(colOf('parts.parameter', 'constraint_type')).toBeUndefined();
|
||||||
expect(rel?.fields.map((f) => f.name)).not.toContain('customer_approval_status');
|
expect(colOf('parts.parameter', 'param_name')?.refersTo).toBeUndefined();
|
||||||
|
expect(colOf('parts.parameter', 'param_name')?.distinct).toBeUndefined();
|
||||||
|
expect(colOf('parts.parameter', 'param_value')?.required).toBe(true);
|
||||||
|
expect(ledgerOf('parts.parameter')?.createOnly).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('UNKNOWN 必有去向:unknown_disposition 钉在 difference_status = unknown 的行上;四态是 ASCII tally', () => {
|
it('案卷两张:part 一单两行(配方遍历的唯一来源)、case 一单一行', () => {
|
||||||
const item = byAddress('substitution', 'difference_item');
|
expect(ledgerOf('sub_eval.part')?.caseKey).toBe('eval_ref');
|
||||||
expect(item?.fields.find((f) => f.name === 'unknown_disposition')?.requiredWhen).toEqual({ column: 'difference_status', value: 'unknown' });
|
expect(ledgerOf('sub_eval.part')?.fields.filter((f) => f.primaryKey).map((f) => f.name)).toEqual(['eval_ref', 'internal_pn']);
|
||||||
expect(item?.fields.find((f) => f.name === 'difference_status')?.tally).toEqual(['match', 'mismatch', 'unknown', 'conditional']);
|
/* 「两颗料是不是同一个类型」于是成了基座数得出来的数:不同取值恰好 1 种 */
|
||||||
expect(item?.fields.find((f) => f.name === 'substitution_relation_id')?.distinct).toBe(true);
|
expect(colOf('sub_eval.part', 'chip_type_id')).toMatchObject({ required: true, distinct: true });
|
||||||
|
expect(colOf('sub_eval.part', 'exists_status')?.tally).toEqual(['found', 'absent']);
|
||||||
|
expect(ledgerOf('sub_eval.case')?.fields.filter((f) => f.primaryKey).map((f) => f.name)).toEqual(['eval_ref']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('P-01 回写五项钉在 current_stage = gate03 那一刻(放行前不拦,放行门 L2 数得出来)', () => {
|
it('P-11 明细带合成列 judgement_class —— 推荐等级要数的是「硬约束不符有几条」', () => {
|
||||||
const p01 = byAddress('demand', 'request');
|
expect(colOf('substitution.difference_item', 'judgement_class')).toMatchObject({
|
||||||
for (const name of ['risk_level', 'verification_requirement', 'output_ref', 'next_action', 'current_activity']) {
|
required: true,
|
||||||
expect(p01?.fields.find((f) => f.name === name)?.requiredWhen, name).toEqual({ column: 'current_stage', value: 'gate03' });
|
tally: ['match', 'soft_mismatch', 'hard_mismatch', 'unknown', 'conditional'],
|
||||||
|
});
|
||||||
|
/* 「判断依据」不是文件引用 —— 不写 P-10 了,名字得说实话 */
|
||||||
|
expect(colOf('substitution.difference_item', 'evidence_note')?.required).toBe(true);
|
||||||
|
expect(colOf('substitution.difference_item', 'evidence_ref')).toBeUndefined();
|
||||||
|
expect(colOf('substitution.difference_item', 'unknown_disposition')?.requiredWhen).toEqual({ column: 'difference_status', value: 'unknown' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('P-11 主记录:退休三列,等级只剩三档', () => {
|
||||||
|
for (const gone of ['substitution_type', 'confidence_score', 'analysis_report_ref']) {
|
||||||
|
expect(colOf('substitution.relation', gone)).toBeUndefined();
|
||||||
|
}
|
||||||
|
expect(colOf('substitution.relation', 'recommendation_level')?.tally).toEqual(['recommended', 'conditional', 'not_recommended']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('P-06:关联的是本单案号(P-01 不在本活动里),类型加了 parametric 一档', () => {
|
||||||
|
expect(colOf('validation.record', 'eval_ref')?.required).toBe(true);
|
||||||
|
expect(colOf('validation.record', 'request_id')).toBeUndefined();
|
||||||
|
expect(colOf('validation.record', 'validation_type')?.tally).toContain('parametric');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('这个活动不碰 P-01 / P-10 / 客户档案', () => {
|
||||||
|
for (const gone of ['demand.request', 'parts.document', 'customer.profile']) {
|
||||||
|
expect(ledgerOf(gone)).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('契约', () => {
|
||||||
|
it('启动两参,都选填 —— 缺了由第一步派工单补', () => {
|
||||||
|
const props = manifest.contract.input.data['properties'] as Record<string, Record<string, unknown>>;
|
||||||
|
expect(Object.keys(props)).toEqual(['originalInternalPn', 'candidateInternalPn']);
|
||||||
|
expect(manifest.contract.input.data['required']).toEqual([]);
|
||||||
|
/* 运营端启动表单:两格都是「可选可新写」的下拉 */
|
||||||
|
for (const key of ['originalInternalPn', 'candidateInternalPn']) {
|
||||||
|
expect(props[key]?.['x-control']).toBe('combo');
|
||||||
|
expect(props[key]?.['x-options']).toMatchObject({ ledger: 'parts.profile', value: 'internal_pn' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('自持单据都带 caseKey eval_ref 且在主键里;放弃候选必须写原因', () => {
|
it('零工具 —— 这是这次重写的立场,不是疏漏', () => {
|
||||||
for (const object of ['pn_check', 'input_gap', 'verdict']) {
|
expect(manifest.contract.resources.tools).toEqual([]);
|
||||||
const t = byAddress(D, object);
|
expect(manifest.declarations.tools).toEqual([]);
|
||||||
expect(t?.kind, object).toBe('record');
|
});
|
||||||
expect(t?.caseKey, object).toBe('eval_ref');
|
|
||||||
expect(t?.fields.find((f) => f.name === 'eval_ref')?.primaryKey, object).toBe(true);
|
it('单一出口,载荷是已落地的事实', () => {
|
||||||
|
expect(manifest.contract.output.mandatory.map((p) => p.name)).toEqual(['assessment-released']);
|
||||||
|
expect(manifest.contract.output.selective).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('判据全读基座数 —— 任何一边都不许是活动自己写的汇总列', () => {
|
||||||
|
const machine = manifest.contract.acceptanceCriteria.filter((c) => c.level === 'L1' || c.level === 'L2');
|
||||||
|
expect(machine.length).toBeGreaterThanOrEqual(5);
|
||||||
|
for (const c of machine) {
|
||||||
|
expect(c.predicate).toBeDefined();
|
||||||
|
/* 基座读数的形状:域_对象_rows / _missing / _distinct_列 / tally 计数 */
|
||||||
|
expect(c.predicate).toMatch(/_(rows|missing|distinct_[a-z_]+|[a-z_]+)\b/);
|
||||||
}
|
}
|
||||||
expect(byAddress(D, 'pn_check')?.fields.find((f) => f.name === 'abandon_reason')?.requiredWhen).toEqual({ column: 'exists_status', value: 'abandoned' });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('三张主数据表标 createOnly:数据工单只建库里没有的料,在库的写下来当场拒(2026-08-27 实测:模型在数据工单里多铺了一行不该建的料号,件上多出一行要人填)', () => {
|
it('预算:调人三次(补数据 / 审核 / 放行),模型只出场一次', () => {
|
||||||
for (const obj of ['profile', 'parameter', 'document'] as const) expect(byAddress('parts', obj)?.createOnly, obj).toBe(true);
|
expect(manifest.contract.resources.budget.humanCalls).toBe(8);
|
||||||
// 凭空拼出来的料号(实测 XL2596S-ADJ)由这一道管:只许建核对表里核过的
|
expect(manifest.contract.authorization.humanCallBudget).toBe(8);
|
||||||
expect(byAddress('parts', 'profile')?.fields.find((f) => f.name === 'internal_pn')?.refersTo).toBe(`${D}.pn_check.internal_pn`);
|
expect(manifest.contract.resources.models).toEqual(['deepseek-v4-flash']);
|
||||||
});
|
|
||||||
|
|
||||||
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']);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -209,7 +270,13 @@ describe('案例模型与清单的对齐', () => {
|
|||||||
expect(n).toContain('custom:');
|
expect(n).toContain('custom:');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('T_release 剧本里 judge criterion 的原文逐字对得上 acceptanceCriteria 的 label', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 →Task 3 补回):`contract.acceptanceCriteria` 已按 spec §7 整体重写,
|
||||||
|
* 旧的九步 T_release 剧本(`case-model.ts`,本任务不动)引的还是旧的三条 L4 label
|
||||||
|
* (「本单数据工单的创建记录」等旧措辞)。Task 3 整体重写四步案例模型时,新 T_release 会照新的
|
||||||
|
* 两条 L4 判据重新引用,那时再放开这条断言。
|
||||||
|
*/
|
||||||
|
it.skip('T_release 剧本里 judge criterion 的原文逐字对得上 acceptanceCriteria 的 label', () => {
|
||||||
const tRelease = (manifest.assets.businessModel.steps as ReadonlyArray<{ item: string; guide?: string }>).find((s) => s.item === 'T_release');
|
const tRelease = (manifest.assets.businessModel.steps as ReadonlyArray<{ item: string; guide?: string }>).find((s) => s.item === 'T_release');
|
||||||
const quoted = [...(tRelease?.guide ?? '').matchAll(/judge criterion="([^"]+)"/g)].map((m) => m[1] ?? '');
|
const quoted = [...(tRelease?.guide ?? '').matchAll(/judge criterion="([^"]+)"/g)].map((m) => m[1] ?? '');
|
||||||
expect(quoted).toHaveLength(3);
|
expect(quoted).toHaveLength(3);
|
||||||
@@ -219,7 +286,13 @@ describe('案例模型与清单的对齐', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('报告模版:编辑面与只读面各就各位', () => {
|
describe('报告模版:编辑面与只读面各就各位', () => {
|
||||||
it('P-08 数据工单件的表 1 + 表 2(标准化参数子表)都可加行可编辑:data-edit / data-edit-add 与主键 data-col 都在', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 → Task 7 补回):`p08-master-create` / `p10-document-create` /
|
||||||
|
* `eval-input-list` 三张局面已随重写从 `assets.viewTemplates` 撤下(spec §8:五张旧件全部作废,
|
||||||
|
* 新的 `input-supplement` 由 Task 7 重做)。`viewHtml()` 现在对这三个名字一律返回空串,
|
||||||
|
* 下面几条对 HTML 结构的断言无从谈起 —— 跳过、不删,等 Task 7–9 补回视图再验。
|
||||||
|
*/
|
||||||
|
it.skip('P-08 数据工单件的表 1 + 表 2(标准化参数子表)都可加行可编辑:data-edit / data-edit-add 与主键 data-col 都在', () => {
|
||||||
const p08 = viewHtml('p08-master-create');
|
const p08 = viewHtml('p08-master-create');
|
||||||
expect(p08).toContain('data-edit="parts.profile"');
|
expect(p08).toContain('data-edit="parts.profile"');
|
||||||
expect(p08).toContain('data-edit-add="parts.profile"');
|
expect(p08).toContain('data-edit-add="parts.profile"');
|
||||||
@@ -234,7 +307,8 @@ describe('报告模版:编辑面与只读面各就各位', () => {
|
|||||||
expect(p10).toContain('data-col="doc_seq"');
|
expect(p10).toContain('data-col="doc_seq"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('评估输入清单:缺口表可编辑(supplement),绑定区只读', () => {
|
/* SKIP(2026-08-29,Task 2 → Task 7):`eval-input-list` 局面已撤下,`sub_eval.input_gap` / `sub_eval.pn_check` 两张单据也已随重写退休(顶掉它们的是 `sub_eval.part` / `sub_eval.case`) */
|
||||||
|
it.skip('评估输入清单:缺口表可编辑(supplement),绑定区只读', () => {
|
||||||
const html = viewHtml('eval-input-list');
|
const html = viewHtml('eval-input-list');
|
||||||
expect(html).toContain(`data-edit="${D}.input_gap"`);
|
expect(html).toContain(`data-edit="${D}.input_gap"`);
|
||||||
expect(html).toContain('data-col="supplement"');
|
expect(html).toContain('data-col="supplement"');
|
||||||
@@ -261,7 +335,8 @@ describe('报告模版:编辑面与只读面各就各位', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('枚举列只用 data-class-of 投影,不摆 ASCII 原值', () => {
|
/* SKIP(2026-08-29,Task 2 → Task 8/9):`difference-review` / `landing-confirm` 两张局面已撤下(新的两张由 Task 8/9 重做);它们绑的字段(recommendation_level 已只剩三档、analysis_status 已不带 tally)到时也会一并对齐 */
|
||||||
|
it.skip('枚举列只用 data-class-of 投影,不摆 ASCII 原值', () => {
|
||||||
expect(viewHtml('difference-review')).toContain('data-class-of="difference_status"');
|
expect(viewHtml('difference-review')).toContain('data-class-of="difference_status"');
|
||||||
expect(viewHtml('difference-review')).toContain('data-class-of="recommendation_level"');
|
expect(viewHtml('difference-review')).toContain('data-class-of="recommendation_level"');
|
||||||
expect(viewHtml('difference-review')).not.toContain('data-slot="difference_status"');
|
expect(viewHtml('difference-review')).not.toContain('data-slot="difference_status"');
|
||||||
@@ -277,7 +352,8 @@ describe('报告模版:编辑面与只读面各就各位', () => {
|
|||||||
await expect(host.base.interaction.pushHtml('<div><script>x()</script></div>')).rejects.toThrow(/无脚本执行权/);
|
await expect(host.base.interaction.pushHtml('<div><script>x()</script></div>')).rejects.toThrow(/无脚本执行权/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('五张模版的外壳都是搬自目标件的 page/card,不再含 E04/I01 那套视觉底(.proto-root)的痕迹', () => {
|
/* SKIP(2026-08-29,Task 2 → Task 7-9):VIEWS 列的五张局面已全部撤下,`assets.viewTemplates` 现在只有运营侧看板一张 */
|
||||||
|
it.skip('五张模版的外壳都是搬自目标件的 page/card,不再含 E04/I01 那套视觉底(.proto-root)的痕迹', () => {
|
||||||
for (const view of VIEWS) {
|
for (const view of VIEWS) {
|
||||||
const html = viewHtml(view);
|
const html = viewHtml(view);
|
||||||
expect(html, view).toContain('class="card-head"');
|
expect(html, view).toContain('class="card-head"');
|
||||||
@@ -298,7 +374,13 @@ describe('报告模版:编辑面与只读面各就各位', () => {
|
|||||||
* ② **缺口表补充内容每一行都摆红「必填」,而闸只认 3 行**——`requiredWhen` 是按行不按列,
|
* ② **缺口表补充内容每一行都摆红「必填」,而闸只认 3 行**——`requiredWhen` 是按行不按列,
|
||||||
* 静态 `class="req"` 把整列都标了必填。
|
* 静态 `class="req"` 把整列都标了必填。
|
||||||
*/
|
*/
|
||||||
describe('徽标别名与值域相关的坑(2026-08-27 实测账,回归网)', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 → Task 7-9 补回):`BADGED` 列表里的每一项都指着已撤下的五张局面
|
||||||
|
* (p08-master-create 等)或已改动的字段(recommendation_level 掉了 alternate 档、
|
||||||
|
* analysis_status 不再带 tally、`sub_eval.pn_check` 整个改名 `sub_eval.part`)。
|
||||||
|
* 这整组「回归网」要等 Task 7–9 把新三张件补回来、字段清单也重新对齐后才谈得上继续钉。
|
||||||
|
*/
|
||||||
|
describe.skip('徽标别名与值域相关的坑(2026-08-27 实测账,回归网)', () => {
|
||||||
/** 必须与 `apps/api/src/modules/interaction/internal/domain/view-render.ts` 的 `classToken` 一字不差 */
|
/** 必须与 `apps/api/src/modules/interaction/internal/domain/view-render.ts` 的 `classToken` 一字不差 */
|
||||||
const vtok = (v: string): string => v.toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 24);
|
const vtok = (v: string): string => v.toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 24);
|
||||||
|
|
||||||
@@ -370,7 +452,12 @@ describe('徽标别名与值域相关的坑(2026-08-27 实测账,回归网)', ()
|
|||||||
* 提交闸永远拦着,这是「墙不配路」的死路形态。
|
* 提交闸永远拦着,这是「墙不配路」的死路形态。
|
||||||
* 两条各配一张回归网,防的是同一类事故再出现在别的字段上。
|
* 两条各配一张回归网,防的是同一类事故再出现在别的字段上。
|
||||||
*/
|
*/
|
||||||
describe('可编辑件的主键与必填列(2026-08-27 复审:主键丢钥匙 / 必填填不了,两条 Critical 回归)', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 → Task 7 补回):`EDITABLE` 列表指着的三张局面(p08-master-create /
|
||||||
|
* p10-document-create / eval-input-list)与 `sub_eval.input_gap` 都已随重写撤下 —— 这两条
|
||||||
|
* Critical 回归网等 Task 7 重做数据工单件时再钉在新件上。
|
||||||
|
*/
|
||||||
|
describe.skip('可编辑件的主键与必填列(2026-08-27 复审:主键丢钥匙 / 必填填不了,两条 Critical 回归)', () => {
|
||||||
/** 三张真能编辑的件与它们各自绑定的台账对象 */
|
/** 三张真能编辑的件与它们各自绑定的台账对象 */
|
||||||
const EDITABLE: ReadonlyArray<{ view: string; domain: string; object: string }> = [
|
const EDITABLE: ReadonlyArray<{ view: string; domain: string; object: string }> = [
|
||||||
{ view: 'p08-master-create', domain: 'parts', object: 'profile' },
|
{ view: 'p08-master-create', domain: 'parts', object: 'profile' },
|
||||||
@@ -448,7 +535,11 @@ describe('两张验收件的主题色', () => {
|
|||||||
* 外层按 relation 逐个复制 <section class="cand">,里层明细只列该候选名下的行(data-by 父键过滤);
|
* 外层按 relation 逐个复制 <section class="cand">,里层明细只列该候选名下的行(data-by 父键过滤);
|
||||||
* 汇总表一个候选一条;品牌从 parts.profile 整表按料号挑(取值,不是算)。
|
* 汇总表一个候选一条;品牌从 parts.profile 整表按料号挑(取值,不是算)。
|
||||||
*/
|
*/
|
||||||
describe('difference-review:一个候选一张表', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 → Task 8 补回):`difference-review` 局面已随重写撤下,
|
||||||
|
* 新版由 Task 8 重做(件② `difference-review` —— 差异评估结论审核)。
|
||||||
|
*/
|
||||||
|
describe.skip('difference-review:一个候选一张表', () => {
|
||||||
const html = viewHtml('difference-review');
|
const html = viewHtml('difference-review');
|
||||||
it('候选块是嵌套行模版:外层 relation、里层明细按 substitution_relation_id 过滤;容器标签不在行模版里再出现', () => {
|
it('候选块是嵌套行模版:外层 relation、里层明细按 substitution_relation_id 过滤;容器标签不在行模版里再出现', () => {
|
||||||
expect(html).toContain('<article class="cands" data-rows="mine:substitution.relation"><section class="cand">');
|
expect(html).toContain('<article class="cands" data-rows="mine:substitution.relation"><section class="cand">');
|
||||||
@@ -469,7 +560,11 @@ describe('difference-review:一个候选一张表', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* 评估输入清单的件上摆「候选参数现值」:一颗料一张、里层按料号过滤、只读(没有 data-edit) */
|
/* 评估输入清单的件上摆「候选参数现值」:一颗料一张、里层按料号过滤、只读(没有 data-edit) */
|
||||||
describe('eval-input-list:候选参数现值只读参考', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 → Task 7 补回):`eval-input-list` 局面已随重写撤下,
|
||||||
|
* 「候选参数现值」这块只读参考并入 Task 7 的 `input-supplement`。
|
||||||
|
*/
|
||||||
|
describe.skip('eval-input-list:候选参数现值只读参考', () => {
|
||||||
it('外层按核对表逐颗复制,里层 parts.parameter 按 internal_pn 过滤,不是可编辑表', () => {
|
it('外层按核对表逐颗复制,里层 parts.parameter 按 internal_pn 过滤,不是可编辑表', () => {
|
||||||
const html = viewHtml('eval-input-list');
|
const html = viewHtml('eval-input-list');
|
||||||
expect(html).toContain(`<article class="prefs" data-rows="mine:${D}.pn_check"><section class="pref">`);
|
expect(html).toContain(`<article class="prefs" data-rows="mine:${D}.pn_check"><section class="pref">`);
|
||||||
@@ -491,7 +586,14 @@ describe('activity-ledger:关键台账数据看板', () => {
|
|||||||
const data = board?.data;
|
const data = board?.data;
|
||||||
const declaredAddresses = new Set(manifest.declarations.ledger.map((l) => `${l.domain}.${l.object}`));
|
const declaredAddresses = new Set(manifest.declarations.ledger.map((l) => `${l.domain}.${l.object}`));
|
||||||
|
|
||||||
it('取数声明:案号台账是 record 且 key 是它的 caseKey、前缀 W05-;每条 rows 的表都已声明;from 只指 case 或前面的 as.列', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 → Task 9 补回):看板的 `data` 声明(`views/ledger-board.ts` 的
|
||||||
|
* `LEDGER_BOARD_DATA`)还圈着已退休的旧台账(`sub_eval.pn_check` / `sub_eval.verdict` /
|
||||||
|
* `demand.request` / `customer.profile` / `parts.document`)—— Task 9「运营中心看板重绑」
|
||||||
|
* 会把它换成新九张表;这条取数声明的自洽性检查到那时再验。html 自身的结构测试(下面三条)
|
||||||
|
* 不依赖 `declarations.ledger`,仍然有效、继续跑。
|
||||||
|
*/
|
||||||
|
it.skip('取数声明:案号台账是 record 且 key 是它的 caseKey、前缀 W05-;每条 rows 的表都已声明;from 只指 case 或前面的 as.列', () => {
|
||||||
expect(data).toBeDefined();
|
expect(data).toBeDefined();
|
||||||
if (data === undefined) return;
|
if (data === undefined) return;
|
||||||
const caseLedger = manifest.declarations.ledger.find((l) => `${l.domain}.${l.object}` === data.case.ledger);
|
const caseLedger = manifest.declarations.ledger.find((l) => `${l.domain}.${l.object}` === data.case.ledger);
|
||||||
|
|||||||
@@ -66,7 +66,14 @@ describe('剧本声明的读写面 ⊆ 清单声明', () => {
|
|||||||
const declaredAddresses = new Set(manifest.declarations.ledger.map((d) => `${d.domain}.${d.object}`));
|
const declaredAddresses = new Set(manifest.declarations.ledger.map((d) => `${d.domain}.${d.object}`));
|
||||||
const declaredTools = new Set(manifest.declarations.tools.map((t) => t.name));
|
const declaredTools = new Set(manifest.declarations.tools.map((t) => t.name));
|
||||||
|
|
||||||
it('steps[].writes / reads(台账那几个)全落在声明过的对象上', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 → Task 3 补回):`manifest.declarations.ledger` 已按
|
||||||
|
* `docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md` §3 整体重写成九张新表,
|
||||||
|
* 而 `ASSESSMENT_STEPS`(`case-model.ts`)还是旧的九步、引用的还是已退休的旧台账地址
|
||||||
|
* (`demand.request` / `customer.profile` / `parts.document` / `sub_eval.pn_check` 等)——
|
||||||
|
* Task 3 整体重写案例模型(四步、零 guide)时,`writes`/`reads` 会重新对齐新的九张表。
|
||||||
|
*/
|
||||||
|
it.skip('steps[].writes / reads(台账那几个)全落在声明过的对象上', () => {
|
||||||
for (const step of ASSESSMENT_STEPS) {
|
for (const step of ASSESSMENT_STEPS) {
|
||||||
for (const address of step.writes) expect(declaredAddresses.has(address), `${step.item} 要写 ${address}`).toBe(true);
|
for (const address of step.writes) expect(declaredAddresses.has(address), `${step.item} 要写 ${address}`).toBe(true);
|
||||||
for (const address of step.reads.filter((r) => r.includes('.') && !r.startsWith('case:'))) {
|
for (const address of step.reads.filter((r) => r.includes('.') && !r.startsWith('case:'))) {
|
||||||
@@ -75,7 +82,12 @@ describe('剧本声明的读写面 ⊆ 清单声明', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('steps[].tools 全部是声明过的工具;只有 S3 调工具', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 → Task 3 补回):`declarations.tools` 已清空(零工具是这次重写的
|
||||||
|
* 立场),而旧的 S3_assess 步骤仍声明着两枚已退役工具的字面量(constants.ts 头注)。
|
||||||
|
* Task 3 重写案例模型时四步「全程零 guide、零工具」,这条断言到时会被新版覆盖。
|
||||||
|
*/
|
||||||
|
it.skip('steps[].tools 全部是声明过的工具;只有 S3 调工具', () => {
|
||||||
for (const step of ASSESSMENT_STEPS) {
|
for (const step of ASSESSMENT_STEPS) {
|
||||||
for (const tool of step.tools) expect(declaredTools.has(tool), `${step.item} 要调 ${tool}`).toBe(true);
|
for (const tool of step.tools) expect(declaredTools.has(tool), `${step.item} 要调 ${tool}`).toBe(true);
|
||||||
if (step.item !== 'S3_assess') expect(step.tools, step.item).toEqual([]);
|
if (step.item !== 'S3_assess') expect(step.tools, step.item).toEqual([]);
|
||||||
@@ -407,7 +419,13 @@ describe('说明书与剧本的骨架', () => {
|
|||||||
expect(g).not.toMatch(/只有它在 \\`parts\.profile\\` 里真有一行/);
|
expect(g).not.toMatch(/只有它在 \\`parts\.profile\\` 里真有一行/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('件上三处样式毛病钉死(2026-08-27 用户:表显示不全不能右滑 / 编辑态内部料号只读字与输入框并排 / input 与 select 没对齐)', () => {
|
/*
|
||||||
|
* SKIP(2026-08-29,Task 2 → Task 7 补回):`p08-master-create` 局面已随重写撤下(它的
|
||||||
|
* 编辑态 CSS 现在无处可查)。共享的横滑规则(`.jc-tbl-wrap, .tbl-wrap { overflow-x: auto;`)
|
||||||
|
* 与中文表头那条(下面一条测试)仍读 `manifest.assets.viewTemplates` 整体、不点名具体局面,
|
||||||
|
* 继续钉着看板;这条要等 Task 7 重做数据工单件才谈得上继续钉编辑态细节。
|
||||||
|
*/
|
||||||
|
it.skip('件上三处样式毛病钉死(2026-08-27 用户:表显示不全不能右滑 / 编辑态内部料号只读字与输入框并排 / input 与 select 没对齐)', () => {
|
||||||
const html = manifest.assets.viewTemplates.find((v) => v.name === 'p08-master-create')?.html ?? '';
|
const html = manifest.assets.viewTemplates.find((v) => v.name === 'p08-master-create')?.html ?? '';
|
||||||
// 两种包装都要能横滑:验收件用目标件的 .tbl-wrap(它的验收版写着 overflow:hidden),2026-08-27 用户在结论审核页撞上
|
// 两种包装都要能横滑:验收件用目标件的 .tbl-wrap(它的验收版写着 overflow:hidden),2026-08-27 用户在结论审核页撞上
|
||||||
// 运营侧的看板也在这条纪律里(它六张表都宽)
|
// 运营侧的看板也在这条纪律里(它六张表都宽)
|
||||||
|
|||||||
@@ -21,7 +21,12 @@
|
|||||||
* 五处不给 doneWhen:S0d_p08_master / S0d_p10_document(触达人)、S1(交人补缺口)、
|
* 五处不给 doneWhen:S0d_p08_master / S0d_p10_document(触达人)、S1(交人补缺口)、
|
||||||
* S4(拍板且可循环)、T_release(签下去)。
|
* S4(拍板且可循环)、T_release(签下去)。
|
||||||
*/
|
*/
|
||||||
import { D, RULE_VERSION, TOOL_COMPARE, TOOL_RECOMMEND } from './constants.ts';
|
/*
|
||||||
|
* `TOOL_COMPARE` / `TOOL_RECOMMEND` 退役(见 constants.ts 头注)—— 下面几处原本 `${TOOL_COMPARE}`
|
||||||
|
* / `${TOOL_RECOMMEND}` 的插值已就地换成同样的字面量,文字与结构一字不改。Task 3 整体重写本文件
|
||||||
|
* (四步、零工具)时会把这些引用连同工具调用一起撤掉,这里只是维持包能编译的最小机械改动。
|
||||||
|
*/
|
||||||
|
import { D, RULE_VERSION } from './constants.ts';
|
||||||
|
|
||||||
export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
|
export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL">
|
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL">
|
||||||
@@ -199,7 +204,7 @@ export const NARRATIVE = `§0 开案:**判断候选料号在技术上能不能
|
|||||||
**四项绑定后不可改**(客户 / 需求 / 原 PN / 候选数组):绑错只能退回启动人重新启动。
|
**四项绑定后不可改**(客户 / 需求 / 原 PN / 候选数组):绑错只能退回启动人重新启动。
|
||||||
|
|
||||||
**能算的不问人也不自己判**:四态(MATCH / MISMATCH / UNKNOWN / CONDITIONAL)与推荐等级
|
**能算的不问人也不自己判**:四态(MATCH / MISMATCH / UNKNOWN / CONDITIONAL)与推荐等级
|
||||||
(推荐 / 条件推荐 / 备选 / 不推荐)全由两枚工具算 —— \`${TOOL_COMPARE}\`、\`${TOOL_RECOMMEND}\`,
|
(推荐 / 条件推荐 / 备选 / 不推荐)全由两枚工具算 —— \`part-substitution-assessment.compare-parameters\`、\`part-substitution-assessment.recommend\`,
|
||||||
一批一次。你负责对照表、证据引用、影响说明、UNKNOWN 的去向。**规则版本 \`${RULE_VERSION}\` 随每条结论落账**。
|
一批一次。你负责对照表、证据引用、影响说明、UNKNOWN 的去向。**规则版本 \`${RULE_VERSION}\` 随每条结论落账**。
|
||||||
|
|
||||||
**UNKNOWN 是一等公民**:查不到的参数值写「未披露」,不许估、不许留空串冒充;每个 UNKNOWN 必须写去向
|
**UNKNOWN 是一等公民**:查不到的参数值写「未披露」,不许估、不许留空串冒充;每个 UNKNOWN 必须写去向
|
||||||
@@ -537,7 +542,7 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis
|
|||||||
writes: ['substitution.relation', 'substitution.difference_item', 'validation.record', `${D}.verdict`],
|
writes: ['substitution.relation', 'substitution.difference_item', 'validation.record', `${D}.verdict`],
|
||||||
reads: ['substitution.relation', 'substitution.difference_item', 'demand.request', `${D}.input_gap`, `${D}.verdict`],
|
reads: ['substitution.relation', 'substitution.difference_item', 'demand.request', `${D}.input_gap`, `${D}.verdict`],
|
||||||
view: 'difference-review',
|
view: 'difference-review',
|
||||||
tools: [TOOL_COMPARE, TOOL_RECOMMEND],
|
tools: ['part-substitution-assessment.compare-parameters', 'part-substitution-assessment.recommend'],
|
||||||
doneWhen:
|
doneWhen:
|
||||||
'substitution_difference_item_missing = 0 and substitution_relation_missing = 0 and ' +
|
'substitution_difference_item_missing = 0 and substitution_relation_missing = 0 and ' +
|
||||||
'substitution_difference_item_difference_status_match + substitution_difference_item_difference_status_mismatch + ' +
|
'substitution_difference_item_difference_status_match + substitution_difference_item_difference_status_mismatch + ' +
|
||||||
@@ -545,7 +550,7 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis
|
|||||||
'tool_part_substitution_assessment_compare_parameters_calls > 0 and tool_part_substitution_assessment_recommend_calls > 0',
|
'tool_part_substitution_assessment_compare_parameters_calls > 0 and tool_part_substitution_assessment_recommend_calls > 0',
|
||||||
guide: `对照表铺好了,**固定三拍**:比对 → 定级 → 写盘。**工具调用后同拍其余动作作废**,别把工具和写台账串在一起;两枚工具各只调一次。
|
guide: `对照表铺好了,**固定三拍**:比对 → 定级 → 写盘。**工具调用后同拍其余动作作废**,别把工具和写台账串在一起;两枚工具各只调一次。
|
||||||
|
|
||||||
【第一拍:只调 \`${TOOL_COMPARE}\`,别的什么都不写】**只报料号与客户约束,一个参数值都不抄** —— 参数、封装、生命周期、证据引用
|
【第一拍:只调 \`part-substitution-assessment.compare-parameters\`,别的什么都不写】**只报料号与客户约束,一个参数值都不抄** —— 参数、封装、生命周期、证据引用
|
||||||
由工具自己从台账现读(parts.parameter / parts.profile / parts.document,本单刚建的也算),每一对都是台账原值:
|
由工具自己从台账现读(parts.parameter / parts.profile / parts.document,本单刚建的也算),每一对都是台账原值:
|
||||||
{"originalInternalPn":"<原 PN>","candidates":[{"candidateInternalPn":"<候选>","requirements":{"<参数名>":"<约束>"}}, …]}
|
{"originalInternalPn":"<原 PN>","candidates":[{"candidateInternalPn":"<候选>","requirements":{"<参数名>":"<约束>"}}, …]}
|
||||||
· 全部候选一次打包;
|
· 全部候选一次打包;
|
||||||
@@ -557,7 +562,7 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis
|
|||||||
不是「1 或 10」**;两颗写着同一段就是一致。出参每项的 readAs 写着工具把两边各读成了什么(范围 / 定值 / 单边界 / 文本),
|
不是「1 或 10」**;两颗写着同一段就是一致。出参每项的 readAs 写着工具把两边各读成了什么(范围 / 定值 / 单边界 / 文本),
|
||||||
写 impact_statement 时按它说,别自己重新解释一遍值。
|
写 impact_statement 时按它说,别自己重新解释一遍值。
|
||||||
|
|
||||||
【第二拍:只调 \`${TOOL_RECOMMEND}\`,别的什么都不做】把第一拍出参里每个候选的 counts 原样打包传进去:{"candidates":[{"candidateInternalPn":"…","counts":{…}}, …]}。调完这一拍结束。
|
【第二拍:只调 \`part-substitution-assessment.recommend\`,别的什么都不做】把第一拍出参里每个候选的 counts 原样打包传进去:{"candidates":[{"candidateInternalPn":"…","counts":{…}}, …]}。调完这一拍结束。
|
||||||
**推荐等级只能由它算**——你自己填一个进 \`substitution.relation.recommendation_level\`,数据面看着像对的,机制上却是你拍的;
|
**推荐等级只能由它算**——你自己填一个进 \`substitution.relation.recommendation_level\`,数据面看着像对的,机制上却是你拍的;
|
||||||
而且这一步的收步判据数着这枚工具的调用次数,不调它这一步永远收不掉。
|
而且这一步的收步判据数着这枚工具的调用次数,不调它这一步永远收不掉。
|
||||||
|
|
||||||
@@ -608,7 +613,7 @@ difference_status / compare_result / risk_level / impact_statement / unknown_dis
|
|||||||
· 意见点名某个参数 → 改那一行 \`substitution.difference_item\` 的 impact_statement / unknown_disposition / evidence_ref;要挂验证的补一行 \`validation.record\`;
|
· 意见点名某个参数 → 改那一行 \`substitution.difference_item\` 的 impact_statement / unknown_disposition / evidence_ref;要挂验证的补一行 \`validation.record\`;
|
||||||
· 意见说推荐理由站不住 → 改 \`substitution.relation.recommendation_reason\`(等级**不许自己填** —— 它只能由工具算;写清楚为什么维持);
|
· 意见说推荐理由站不住 → 改 \`substitution.relation.recommendation_reason\`(等级**不许自己填** —— 它只能由工具算;写清楚为什么维持);
|
||||||
· 意见说**某条判定本身错了**(客户约束译错、或原 PN 自己也不满足的客户线被记在候选头上)→ 改对 requirements
|
· 意见说**某条判定本身错了**(客户约束译错、或原 PN 自己也不满足的客户线被记在候选头上)→ 改对 requirements
|
||||||
(值不归你改:参数是台账原值,工具自己读),再**重新调 \`${TOOL_COMPARE}\` 与 \`${TOOL_RECOMMEND}\`**(这一轮各再调一次,
|
(值不归你改:参数是台账原值,工具自己读),再**重新调 \`part-substitution-assessment.compare-parameters\` 与 \`part-substitution-assessment.recommend\`**(这一轮各再调一次,
|
||||||
工具调用后同拍其余动作作废,下一拍照出参覆盖写四态、风险、备注与推荐等级 —— 这是等级唯一合法的改法);
|
工具调用后同拍其余动作作废,下一拍照出参覆盖写四态、风险、备注与推荐等级 —— 这是等级唯一合法的改法);
|
||||||
· 没写具体意见("reject")→ 自己挑最站不住的一处改实(证据最含糊的那条 evidence_ref、去向最空的那条 unknown_disposition),**至少改对一处**;
|
· 没写具体意见("reject")→ 自己挑最站不住的一处改实(证据最含糊的那条 evidence_ref、去向最空的那条 unknown_disposition),**至少改对一处**;
|
||||||
然后**换一种问法**再发一张决策卡 —— **ask 的开头必须写明这一轮改了什么**(与上一张字面不同;
|
然后**换一种问法**再发一张决策卡 —— **ask 的开头必须写明这一轮改了什么**(与上一张字面不同;
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
/** 清单与案例模型共用的常量 —— 单独成文件,免得 manifest ↔ case-model 循环 import 撞 TDZ。 */
|
/** 清单与案例模型共用的常量 —— 单独成文件,免得 manifest ↔ case-model 循环 import 撞 TDZ。 */
|
||||||
export const ACTIVITY_ID_TEXT = 'part-substitution-assessment';
|
export const ACTIVITY_ID_TEXT = 'part-substitution-assessment';
|
||||||
/** 活动自持单据的域名 */
|
/** 活动自持单据(案卷)的域名 */
|
||||||
export const D = 'sub_eval';
|
export const D = 'sub_eval';
|
||||||
/** GOV 差异规则版本 —— 随包冻结,随每条结论落账(rule_version) */
|
/** GOV 差异规则版本 —— 随包冻结,随每条结论落账(rule_version) */
|
||||||
export const RULE_VERSION = 'R-2026.07';
|
export const RULE_VERSION = 'R-2026.07';
|
||||||
export const TOOL_COMPARE = 'part-substitution-assessment.compare-parameters';
|
/*
|
||||||
export const TOOL_RECOMMEND = 'part-substitution-assessment.recommend';
|
* `TOOL_COMPARE` / `TOOL_RECOMMEND` 两个常量已随 2026-08-29 的整体重写退役
|
||||||
|
* (spec `docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md` §1/§8):
|
||||||
|
* 逐项四态改由 S2 唯一一次 `analyze` 出场判定,推荐等级改由配方按 §3.5 的规则算 ——
|
||||||
|
* 两枚工具在工具注册表里的停用排在 Task 10。案例模型(`case-model.ts`)里两处引用
|
||||||
|
* 已就地改写成字面量,Task 3 整体重写该文件时会连同工具调用一起撤掉。
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,87 +1,49 @@
|
|||||||
/**
|
/**
|
||||||
* 活动包清单 —— ACT-WORK-05 差异评估(案例活动)。
|
* 活动包清单 —— ACT-WORK-05 差异评估(案例活动)。
|
||||||
*
|
*
|
||||||
* 业务出处:`docs/SS02-ACT-WORK-05-html/`(活动流程与工单说明 / 一键启动入参说明 /
|
* **2026-08-29 整体重写(@2.0.0)**:从「九步、两枚工具、全程 agent 驱动」改成「四步、零工具、
|
||||||
* 《SS02-SS03-SS04_Demo台账字段细化与统一方案》/ 四份目标件 / 全流程分支图)。
|
* 零 `guide`、AI 出场恰好一次」。设计权威:
|
||||||
* 骨架照 E04 / I01 已验证的样子:双级入参 → 活动自持单据 + 共享主数据 → decide 循环做中间审核 →
|
* `docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md`(下称「spec」)。
|
||||||
* 终局放行。三处与前两个活动刻意不同,写在这里免得下一个读者当成疏漏:
|
* 用户原话立的场:「不是把所有东西都丢给 ai 来处理…一定是系统先确定性的从数据库中取出数据后交给
|
||||||
* ① **主数据是七张(P-08 主 + P-08 参数子表 / P-10 / P-01 / P-11 主+子 / P-06)**,物理表由平台基线
|
* ai 分析」——`docs/ACT-WORK-05-端到端测试记录.md` 的 A–AE 发现表里,八条事故都落在
|
||||||
* (`apps/api/src/app/seed-mvp-data.ts`)物化,这里只**追加**本活动要用的列与标记
|
* 「本来就该是确定的」那几步(编号抄别单、判在库判错、数据工单派给在库的料…),
|
||||||
* (required / tally / refersTo / requiredWhen 是活动的授权面;声明合并是纯 add-column)。
|
* 真正需要判断的只有一件事:**这一条参数,候选跟原厂比算不算事**。
|
||||||
* **P-08 的标准化参数集是独立子表 `parts.parameter`**(2026-08-27 结构改:用户核对实跑
|
*
|
||||||
* 页面后指出必须与目标件结构一致——目标件《数据工单-01》本就是两张表,塞进一个文本列
|
* 这份清单是 Task 2 的产物,只动 `declarations.ledger` 与 `contract` 两段,以及 `assets`
|
||||||
* 违反《统一方案》§六「重复明细用子表承载,不在单一文本字段堆叠」)。
|
* 里已经不自洽的那几处(spec §8 的退役清单前移到这一步,见「计划裁定 G3」)。**案例模型
|
||||||
* ② **数据工单**(PN 不在库时让人从零建整条主数据)= 零输入执行工单 + 工作区的件
|
* (`case-model.ts`)还是旧的九步、旧的 `guide` 文本** —— 下一个任务(Task 3)才整体重写成
|
||||||
* (`mine:parts.profile` / `mine:parts.parameter` / `mine:parts.document` 可加行),
|
* spec §4 的四步(`S1_inputs` / `S2_assess` / `S3_review` / `T_release`)+ 四道哨兵 + 零 `guide`
|
||||||
* 确认落草稿层、放行并入正式表(守铁律②;文档里「确认即入库」在本平台落成「无审核环节、
|
* (行为改由 `PlanStep.auto` 声明式配方接管)。旧剧本引用本次删掉的台账地址而导致的包内自测红,
|
||||||
* 放行并入」)。一张台账表一张单(P-08 主记录 + 参数子表算同一张数据工单 —— 目标件是
|
* 已按台账逐条 `it.skip` 并注明「Task 3 会补回」,不删测试(§12.5 铁律)。
|
||||||
* 同一份工单卡里的两张表)。
|
*
|
||||||
* ③ 逐项四态与推荐等级**由两枚纯工具算**(规则同时以 D01/D02 两张 DMN 随包冻结当文档),
|
* ## 九张台账声明(spec §3)
|
||||||
* 模型只负责对照表、证据与影响说明 —— 硬约束 MISMATCH 不可能被措辞改判。
|
* 两张新字典(`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, DmnModel } from '@contracts';
|
import type { ActivityManifest } from '@contracts';
|
||||||
import { activityIdSchema } from '@contracts';
|
import { activityIdSchema } from '@contracts';
|
||||||
import { ASSESSMENT_SKILLS } from './skills.ts';
|
|
||||||
import { CMMN_XML, NARRATIVE, ASSESSMENT_PHASES, ASSESSMENT_STEPS } from './case-model.ts';
|
import { CMMN_XML, NARRATIVE, ASSESSMENT_PHASES, ASSESSMENT_STEPS } from './case-model.ts';
|
||||||
import { P08_MASTER_CREATE, P10_DOCUMENT_CREATE, EVAL_INPUT_LIST, DIFFERENCE_REVIEW, LANDING_CONFIRM } from './views/report.ts';
|
|
||||||
import { LEDGER_BOARD, LEDGER_BOARD_DATA } from './views/ledger-board.ts';
|
import { LEDGER_BOARD, LEDGER_BOARD_DATA } from './views/ledger-board.ts';
|
||||||
import { ACTIVITY_ID_TEXT, D, TOOL_COMPARE, TOOL_RECOMMEND } from './constants.ts';
|
import { ACTIVITY_ID_TEXT, D } from './constants.ts';
|
||||||
|
|
||||||
export { D, RULE_VERSION, TOOL_COMPARE, TOOL_RECOMMEND } from './constants.ts';
|
export { D, RULE_VERSION } from './constants.ts';
|
||||||
const ACTIVITY_ID = activityIdSchema.parse(ACTIVITY_ID_TEXT);
|
const ACTIVITY_ID = activityIdSchema.parse(ACTIVITY_ID_TEXT);
|
||||||
|
|
||||||
/**
|
|
||||||
* 两张判定表 —— **文档形态**(求值器在 `apps/api/src/app/tools/first-party.ts`,与这里同一份规则):
|
|
||||||
* D01 逐参数四态、D02 推荐等级。都是 FIRST:规则从上往下第一条命中即出结论,顺序就是优先级。
|
|
||||||
*/
|
|
||||||
export const D01_DMN: DmnModel['tables'][number] = {
|
|
||||||
id: 'd01-difference-status',
|
|
||||||
label: '差异四态 D01(R-2026.07)',
|
|
||||||
hitPolicy: 'FIRST',
|
|
||||||
inputs: [
|
|
||||||
{ name: 'compare_result', expression: 'compare_result' },
|
|
||||||
{ name: 'constraint_type', expression: 'constraint_type' },
|
|
||||||
{ name: 'evidence_present', expression: 'evidence_present' },
|
|
||||||
],
|
|
||||||
outputs: [{ name: 'difference_status' }, { name: 'risk_level' }],
|
|
||||||
rules: [
|
|
||||||
{ id: 'd01-u', when: ['"unknown"', '-', '-'], then: ['unknown', 'medium'], annotation: '一侧未披露 → UNKNOWN,挂验证任务' },
|
|
||||||
{ id: 'd01-m0', when: ['"equal","covers"', '"hard"', 'false'], then: ['conditional', 'medium'], annotation: '硬约束缺证据不判 MATCH' },
|
|
||||||
{ id: 'd01-m', when: ['"equal","covers"', '-', '-'], then: ['match', 'low'], annotation: '一致或候选范围盖住原范围' },
|
|
||||||
{ id: 'd01-c', when: ['"configurable","partial"', '-', '-'], then: ['conditional', 'medium'], annotation: '可调配成 / 部分重叠 → 条件' },
|
|
||||||
{ id: 'd01-x', when: ['"narrower","differs"', '"hard"', '-'], then: ['mismatch', 'high'], annotation: '硬约束不一致' },
|
|
||||||
{ id: 'd01-s', when: ['"narrower","differs"', '"soft"', '-'], then: ['conditional', 'medium'], annotation: '软约束不一致 → 评估影响' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const D02_DMN: DmnModel['tables'][number] = {
|
|
||||||
id: 'd02-recommendation',
|
|
||||||
label: '推荐等级 D02(R-2026.07)',
|
|
||||||
hitPolicy: 'FIRST',
|
|
||||||
inputs: [
|
|
||||||
{ name: 'hard_mismatch_total', expression: 'hard_mismatch_total' },
|
|
||||||
{ name: 'unknown_total', expression: 'unknown_total' },
|
|
||||||
{ name: 'configurable_total', expression: 'configurable_total' },
|
|
||||||
{ name: 'conditional_total', expression: 'conditional_total' },
|
|
||||||
],
|
|
||||||
outputs: [{ name: 'recommendation_level' }],
|
|
||||||
rules: [
|
|
||||||
{ id: 'd02-n', when: ['> 0', '-', '-', '-'], then: ['not_recommended'], annotation: '硬约束失败直接不推荐' },
|
|
||||||
{ id: 'd02-u', when: ['0', '> 0', '-', '-'], then: ['conditional'], annotation: '有 UNKNOWN → 等验证闭环' },
|
|
||||||
{ id: 'd02-a', when: ['0', '0', '> 0', '-'], then: ['alternate'], annotation: '只靠改外围配得成 → 备选' },
|
|
||||||
{ id: 'd02-c', when: ['0', '0', '0', '> 0'], then: ['conditional'], annotation: '其余条件项 → 条件推荐' },
|
|
||||||
{ id: 'd02-r', when: ['0', '0', '0', '0'], then: ['recommended'], annotation: '全 MATCH → 推荐' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ASSESSMENT_DMN: DmnModel = {
|
|
||||||
drd: [
|
|
||||||
{ decision: 'd01-difference-status', requires: [], inputs: ['compare_result', 'constraint_type', 'evidence_present'] },
|
|
||||||
{ decision: 'd02-recommendation', requires: [], inputs: ['hard_mismatch_total', 'unknown_total', 'configurable_total', 'conditional_total'] },
|
|
||||||
],
|
|
||||||
tables: [D01_DMN, D02_DMN],
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 字段声明的短写(与 E04 同一套写法:显式 primaryKey / masked,其余按需) */
|
/** 字段声明的短写(与 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 } };
|
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 });
|
const col = (f: F) => ({ primaryKey: false, masked: false, ...f });
|
||||||
@@ -90,7 +52,8 @@ export const manifest: ActivityManifest = {
|
|||||||
identity: {
|
identity: {
|
||||||
activityId: ACTIVITY_ID,
|
activityId: ACTIVITY_ID,
|
||||||
name: 'ACT-WORK-05 · 差异评估',
|
name: 'ACT-WORK-05 · 差异评估',
|
||||||
packageVersion: '1.7.3',
|
/* 重写不是改 —— 同版重装是 no-op(静默丢失,踩过) */
|
||||||
|
packageVersion: '2.0.0',
|
||||||
category: '需求分析与方案匹配',
|
category: '需求分析与方案匹配',
|
||||||
sdkVersion: '0.1.0-m5',
|
sdkVersion: '0.1.0-m5',
|
||||||
checksum: 'dev-checksum-part-substitution-assessment',
|
checksum: 'dev-checksum-part-substitution-assessment',
|
||||||
@@ -102,164 +65,123 @@ export const manifest: ActivityManifest = {
|
|||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
input: {
|
input: {
|
||||||
/*
|
/*
|
||||||
* 一键启动的表单只摆 3 格(2026-08-28 用户定):需求 / 原 PN / 候选 PN 数组 ——
|
* 两格,都选填(spec §4.1):库里有就选,没有就自己写一个新的 —— 启动后由第一步的
|
||||||
* 客户编码、客户名称两格从**启动表单**撤下,上下文都写进「需求」那一格。
|
* 配方核对存在性、缺了就派数据工单补齐。运营端启动表单渲染这两格为
|
||||||
* 应用场景、BOM 位、替代类型、规则版本、负责人期限**不是入参** —— 启动后在执行工单 01 里补。
|
* 「选择 + 新增」下拉(`x-control: combo`),候选从 `parts.profile.internal_pn`
|
||||||
* datasheet 选填文件:传了 AI 在数据工单件上预填,没传人全填(文档 0c:同为合法路径)。
|
* 现读(`x-options`);`x-order` 定表单次序(清单存在 jsonb 里,书写顺序到不了运营端)。
|
||||||
*
|
* requestDescription / candidateInternalPns(数组)/ datasheets 三键已随重写撤下 ——
|
||||||
* ⚠️ **活动内部仍认客户**:说明书与 S0 剧本按 `customerCode` 查客户档案、P-01 的 `customer_code`
|
* 这一版没有「需求主记录」「N 候选并行」「预填 datasheet」这几件事。
|
||||||
* 是必填且引用 `customer.profile` —— 测试台 / 样例仍带着这两个键,那条路照旧;
|
|
||||||
* 而**运营端启动的单没有这两个键**,S0 到客户那一步会缺料。要让运营端也跑通,得另定客户从哪儿来。
|
|
||||||
*/
|
*/
|
||||||
data: {
|
data: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
/* 2026-08-28 用户定:启动表单**一格必填都不设**(三格全选填) */
|
|
||||||
required: [],
|
required: [],
|
||||||
/*
|
|
||||||
* 运营端「启动活动」表单照这几个字段渲染(2026-08-27,`docs/API文档-活动模块.md` §4):
|
|
||||||
* `x-control` 定控件(combo = 选择 + 新增,textarea = 多行),`x-options` 指候选从哪张正式台账现读,`x-order` 定表单次序
|
|
||||||
* (清单存在 jsonb 里,书写顺序到不了运营端 —— PG 会按键长重排),
|
|
||||||
* `description` 是控件下那句辅助说明。台账里没有的值**照样启动**,启动后由 S0 的存在性核对派数据工单。
|
|
||||||
* datasheets 是文件,本期不上启动表单(在数据工单里传)。
|
|
||||||
*/
|
|
||||||
properties: {
|
properties: {
|
||||||
requestDescription: {
|
|
||||||
type: 'string',
|
|
||||||
title: '需求',
|
|
||||||
description: '这一单要评估什么、什么应用、什么约束,原话照写(客户、场景、温域、上限都写在这一格里);据此新建 P-01 需求主记录(版本 V1)。',
|
|
||||||
examples: ['工业电源项目在用 LM2596S-5.0(TI,12V→5V),原厂 PCN 转 NRND,要求验证替代料;应用 12V→5V、工业温域、输入上限 40V'],
|
|
||||||
'x-control': 'textarea',
|
|
||||||
'x-order': 1,
|
|
||||||
},
|
|
||||||
originalInternalPn: {
|
originalInternalPn: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: '原内部料号',
|
title: '原内部料号',
|
||||||
description: '差异评估基准;库里没有该 PN 也可启动,启动后派数据工单补参数。',
|
description: '差异评估基准料号;库里有就选,没有就自己写一个新的,启动后由第一步派数据工单补齐。',
|
||||||
examples: ['LM2596S-5.0'],
|
examples: ['LM2596S-5.0'],
|
||||||
'x-control': 'combo',
|
'x-control': 'combo',
|
||||||
'x-options': { ledger: 'parts.profile', value: 'internal_pn', label: 'internal_pn' },
|
'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,
|
'x-order': 2,
|
||||||
},
|
},
|
||||||
candidateInternalPns: {
|
|
||||||
type: 'array',
|
|
||||||
title: '候选内部料号',
|
|
||||||
description: '被评估物料;库里有就直接选,没有就自己填料号,启动后经数据工单创建。',
|
|
||||||
items: { type: 'string' },
|
|
||||||
examples: [['XL2596S-5.0', 'LM2596S-ADJ']],
|
|
||||||
/* 运营端启动表单只选一颗(2026-08-27 用户定):摆成与原料号同款的下拉;提交的单个料号由后端包成一元数组,活动契约不变 */
|
|
||||||
'x-control': 'combo',
|
|
||||||
'x-options': { ledger: 'parts.profile', value: 'internal_pn', label: 'internal_pn' },
|
|
||||||
'x-order': 3,
|
|
||||||
},
|
|
||||||
datasheets: {
|
|
||||||
type: 'array',
|
|
||||||
format: 'file',
|
|
||||||
title: '候选 datasheet(选填;传了 AI 在数据工单里预填,没传人全填)',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
name: { type: 'string', title: '文件名' },
|
|
||||||
contentType: { type: 'string', title: '类型' },
|
|
||||||
size: { type: 'integer', title: '字节数' },
|
|
||||||
ref: { type: 'string', title: '存档号' },
|
|
||||||
text: { type: 'string', title: '读出来的文本' },
|
|
||||||
note: { type: 'string', title: '读取说明' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
messageAllowed: true,
|
messageAllowed: true,
|
||||||
messageHint: 'FAE 可在此说明客户的应用边界、验证条件或本轮评估的特殊要求',
|
messageHint: 'FAE 可在此说明客户的应用边界或验证条件',
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
/* 单一必发口:本活动终点是 ACT-GATE-03 评审放行;下游 ACT-WORK-08 尚未实现,按「契约先立」声明 */
|
/* 单一必发口:签下去 P-11 落账、P-06 落账;载荷是已落地的事实,不是待办 */
|
||||||
mandatory: [
|
mandatory: [
|
||||||
{
|
{
|
||||||
name: 'assessment-released',
|
name: 'assessment-released',
|
||||||
description: '差异评估结论已落账(P-11 替代关系主记录 + 差异明细、P-01 回写)—— 供 ACT-WORK-08 证据归集消费;当前无消费者,载荷 = 已落地事实',
|
description: '差异评估结论已落账(P-11 替代关系主记录 + 差异明细、P-06 验证记录)—— 载荷即已落地的事实',
|
||||||
payload: {
|
payload: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['evalRef', 'requestId', 'relationTotal'],
|
required: ['evalRef', 'relationId', 'originalPn', 'candidatePn', 'recommendationLevel', 'matchTotal', 'mismatchTotal', 'unknownTotal', 'conditionalTotal', 'validationId'],
|
||||||
properties: {
|
properties: {
|
||||||
evalRef: { type: 'string', title: '评估案号' },
|
evalRef: { type: 'string', title: '评估案号' },
|
||||||
requestId: { type: 'string', title: '需求编号(P-01)' },
|
relationId: { type: 'string', title: '替代关系ID' },
|
||||||
relationTotal: { type: 'integer', title: '落账的替代关系条数' },
|
originalPn: { type: 'string', title: '原内部料号' },
|
||||||
recommendedTotal: { type: 'integer', title: '推荐' },
|
candidatePn: { type: 'string', title: '候选内部料号' },
|
||||||
conditionalTotal: { type: 'integer', title: '条件推荐' },
|
recommendationLevel: { type: 'string', title: '推荐等级' },
|
||||||
alternateTotal: { type: 'integer', title: '备选' },
|
matchTotal: { type: 'integer', title: 'MATCH 条数' },
|
||||||
notRecommendedTotal: { type: 'integer', title: '不推荐' },
|
mismatchTotal: { type: 'integer', title: 'MISMATCH 条数' },
|
||||||
|
unknownTotal: { type: 'integer', title: 'UNKNOWN 条数' },
|
||||||
|
conditionalTotal: { type: 'integer', title: 'CONDITIONAL 条数' },
|
||||||
|
validationId: { type: 'string', title: '关联验证ID' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selective: [],
|
selective: [],
|
||||||
},
|
},
|
||||||
|
/* 一份成果常不止一张表:差异评估结论由明细 + 关系两张共同承载,落账内容由关系 + 验证记录承载 */
|
||||||
requiredDeliverables: [
|
requiredDeliverables: [
|
||||||
{ key: 'eval-input', label: '评估输入清单', ledger: `${D}.input_gap`, ledgers: [`${D}.input_gap`, `${D}.pn_check`, `${D}.verdict`], editableAtRelease: [] },
|
{ key: 'difference-items', label: '差异评估结论', ledger: 'substitution.difference_item', ledgers: ['substitution.difference_item', 'substitution.relation'], editableAtRelease: [] },
|
||||||
{ key: 'difference-items', label: '差异评估结论(P-11 差异明细)', ledger: 'substitution.difference_item', ledgers: ['substitution.difference_item', 'substitution.relation'], editableAtRelease: [] },
|
{ key: 'landing', label: '落账内容', ledger: 'substitution.relation', ledgers: ['substitution.relation', 'validation.record'], editableAtRelease: [] },
|
||||||
{ key: 'landing', label: '拟落账内容(P-11 主记录 + P-01 回写)', ledger: 'substitution.relation', ledgers: ['substitution.relation', 'demand.request'], editableAtRelease: [] },
|
|
||||||
],
|
],
|
||||||
/*
|
/*
|
||||||
* 验收判据 —— L1/L2 只读基座读数;L4 三条是文档验收工单 01/02 里留给人的裁量;
|
* 验收判据(spec §7)—— 五条机检 L1/L2 全读基座数,两条 L4 裁量,一条 L5 责任关(不上卡)。
|
||||||
* 「推荐等级与明细一致」「硬约束不判 MATCH」机器已由工具守住,这里仍留 L4 让签字的人过目。
|
* `input-ready` 的谓词与 S1(Task 3 待写)的 `doneWhen` 同构:两颗都在库、类型一致、参数不缺 ——
|
||||||
|
* **不写「参数行数 = 字段数×2」**,那不是基座数得出来的数(`parts_type_param_rows` 数的是
|
||||||
|
* 本单写下的字段行,不是整本字典;给判据编一个数不出来的数,是这个仓踩过六次的同一个坑)。
|
||||||
*/
|
*/
|
||||||
acceptanceCriteria: [
|
acceptanceCriteria: [
|
||||||
{
|
{
|
||||||
key: 'input-confirmed',
|
key: 'input-ready',
|
||||||
label: '评估输入已经 PW 补齐确认:执行工单 01 已交回,缺口行的补充内容都有,补不了的留了 UNKNOWN',
|
label: '评估输入已就绪:原 PN 与候选都在库、归了同一个芯片类型、主数据与参数值都不缺必填',
|
||||||
level: 'L1',
|
level: 'L1',
|
||||||
predicate: `input_confirmed_total > 0 and ${D}_input_gap_rows > 0 and ${D}_input_gap_missing = 0`,
|
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: 'review-approved',
|
key: 'every-param-judged',
|
||||||
label: '差异评估结论已经 FAE/RI 审核通过:验收工单 01 已过,不处在驳回待改状态',
|
label: '每条核心参数都有明确状态:每条差异明细都是 MATCH / MISMATCH / UNKNOWN / CONDITIONAL 之一,没有一条缺必填',
|
||||||
level: 'L1',
|
|
||||||
predicate: 'review_approved_total > 0 and substitution_difference_item_rows > 0 and substitution_difference_item_missing = 0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'every-candidate-assessed',
|
|
||||||
label: '每个未放弃的候选都有一条替代关系记录(N 候选 = N 条 P-11,并行独立落账)',
|
|
||||||
level: 'L2',
|
|
||||||
predicate: `substitution_relation_rows > 0 and substitution_relation_rows + ${D}_pn_check_exists_status_abandoned + 1 = ${D}_pn_check_rows`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'every-item-has-status',
|
|
||||||
label: '关键参数差异均有明确状态:每条明细都是 MATCH / MISMATCH / UNKNOWN / CONDITIONAL 之一',
|
|
||||||
level: 'L2',
|
level: 'L2',
|
||||||
predicate:
|
predicate:
|
||||||
'substitution_difference_item_rows > 0 and substitution_difference_item_difference_status_match + substitution_difference_item_difference_status_mismatch + ' +
|
'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',
|
'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',
|
key: 'unknown-has-disposition',
|
||||||
label: 'UNKNOWN 项均有去向:每个 UNKNOWN 都挂了验证任务或补来源动作,影响说明与证据引用逐条齐全',
|
label: '每个 UNKNOWN 都有去向:去向、影响说明、判断依据没有一条缺',
|
||||||
level: 'L2',
|
level: 'L2',
|
||||||
predicate: 'substitution_difference_item_missing = 0',
|
predicate: 'substitution_difference_item_missing = 0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'judged-by-rule',
|
key: 'validation-landed',
|
||||||
label: '四态与推荐等级由规则算出:逐参数比对工具与推荐定级工具都真调过',
|
label: '验证记录已落账:P-06 恰有一行且不缺必填(全推荐也要写「本轮无待验项」)',
|
||||||
level: 'L2',
|
level: 'L2',
|
||||||
predicate: `tool_part_substitution_assessment_compare_parameters_calls > 0 and tool_part_substitution_assessment_recommend_calls > 0`,
|
predicate: 'validation_record_rows = 1 and validation_record_missing = 0',
|
||||||
},
|
},
|
||||||
{
|
{ key: 'evidence-credible', label: '证据链可信:每条差异的影响说明与判断依据站得住', level: 'L4' },
|
||||||
key: 'analysis-status-confirmed',
|
{ key: 'values-traceable', label: '参数值与原厂资料对得上:候选与原 PN 的取值来源可追溯', level: 'L4' },
|
||||||
label: '分析状态与客户批准状态分离:每条 P-11 只写分析层 analysis_status = FAE 确认,规则版本已锁定随结论落账',
|
|
||||||
level: 'L2',
|
|
||||||
predicate: 'substitution_relation_analysis_status_fae_confirmed = substitution_relation_rows and substitution_relation_missing = 0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'p01-writeback-complete',
|
|
||||||
label: '回写 P-01 字段完整:风险等级、验证要求、输出链接、下一步动作、当前阶段五项齐全',
|
|
||||||
level: 'L2',
|
|
||||||
predicate: 'demand_request_rows > 0 and demand_request_missing = 0',
|
|
||||||
},
|
|
||||||
{ key: 'evidence-credible', label: '证据链可信:每条结论都能追溯到 P-08 / P-10 / P-06 的具体记录,或本单数据工单的创建记录(人工录入)', level: 'L4' },
|
|
||||||
{ key: 'hard-not-auto-match', label: '硬约束项未被自动判 MATCH:封装 / 工艺 / 电气边界等硬约束的 MATCH 都有证据支持', level: 'L4' },
|
|
||||||
{ key: 'recommendation-consistent', label: '推荐等级与差异明细一致:存在 UNKNOWN / CONDITIONAL 的候选没有给「推荐」', level: 'L4' },
|
|
||||||
/* L5 是责任关:那张卡本身就是这道关,卡面上不摆它(基座统一滤) */
|
/* L5 是责任关:那张卡本身就是这道关,卡面上不摆它(基座统一滤) */
|
||||||
{ key: 'gate03-release', label: 'ACT-GATE-03 评审放行:签下去 P-11 落账、P-01 回写,结论供下游消费', level: 'L5' },
|
{ key: 'gate03-release', label: 'ACT-GATE-03 评审放行:签下去 P-11 落账、P-06 落账,结论供下游消费', level: 'L5' },
|
||||||
],
|
],
|
||||||
revisableAtRelease: [],
|
revisableAtRelease: [],
|
||||||
prohibitions: [
|
prohibitions: [
|
||||||
@@ -267,163 +189,160 @@ export const manifest: ActivityManifest = {
|
|||||||
{ key: 'no-override-hard-mismatch', statement: '硬约束 MISMATCH 不得被任何措辞改判 MATCH;要改只能补证据后重比' },
|
{ key: 'no-override-hard-mismatch', statement: '硬约束 MISMATCH 不得被任何措辞改判 MATCH;要改只能补证据后重比' },
|
||||||
{ key: 'no-customer-approval-write', statement: '客户批准状态由独立审批流维护,本活动不写 customer_approval_status(没有这一列的写权)' },
|
{ key: 'no-customer-approval-write', statement: '客户批准状态由独立审批流维护,本活动不写 customer_approval_status(没有这一列的写权)' },
|
||||||
{ key: 'no-commercial-judgement', statement: '本活动只答技术可替代性:不做数量、价格、交期、采购判断' },
|
{ key: 'no-commercial-judgement', statement: '本活动只答技术可替代性:不做数量、价格、交期、采购判断' },
|
||||||
{ key: 'no-rebind', statement: '启动绑定的 4 项(客户 / 需求 / 原 PN / 候选数组)工单内不可改;绑错只能退回触发方重新启动' },
|
|
||||||
{ key: 'no-external-write', statement: '放行前不得触达外部系统 —— 触达世界的动作集中在放行那一刻' },
|
|
||||||
],
|
],
|
||||||
authorization: {
|
authorization: {
|
||||||
discretion: [],
|
discretion: [],
|
||||||
preauthorizedActions: [],
|
preauthorizedActions: [],
|
||||||
/*
|
/* 调人三次(补数据 / 审核 / 放行);零工具、AI 只出场一次 —— 预算随之从 16/90 步收紧到 8/40 步 */
|
||||||
* 基线:数据工单 ≤2 + 候选放弃/创建决策 ≤2 + 新客户录入卡 1 + 执行单 01 + 审核决策 1 + 放行 1 = 8;
|
humanCallBudget: 8,
|
||||||
* 审核循环预留两轮(+4)= 12。cap 必须严格大于最后一次拍板时的实际次数(E04 实测栽过),留到 16。
|
|
||||||
*/
|
|
||||||
humanCallBudget: 16,
|
|
||||||
},
|
},
|
||||||
resources: {
|
resources: {
|
||||||
tools: [{ name: TOOL_COMPARE }, { name: TOOL_RECOMMEND }],
|
tools: [],
|
||||||
skills: ASSESSMENT_SKILLS.map((s) => s.id),
|
skills: [],
|
||||||
models: ['deepseek-v4-flash'],
|
models: ['deepseek-v4-flash'],
|
||||||
roles: ['FAE/RI'],
|
roles: ['FAE/RI'],
|
||||||
budget: { steps: 90, tokens: 2_000_000, activeMinutes: 120, humanCalls: 16 },
|
budget: { steps: 40, tokens: 300_000, activeMinutes: 60, humanCalls: 8 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/* ── 声明 ────────────────────────────────────────────────────── */
|
/* ── 声明 ────────────────────────────────────────────────────── */
|
||||||
declarations: {
|
declarations: {
|
||||||
models: ['deepseek-v4-flash'],
|
models: ['deepseek-v4-flash'],
|
||||||
tools: [{ name: TOOL_COMPARE }, { name: TOOL_RECOMMEND }],
|
/* 零工具 —— 这是这次重写的立场,不是疏漏(见头注) */
|
||||||
|
tools: [],
|
||||||
ledger: [
|
ledger: [
|
||||||
/* L-02 客户主台账:只在分支 0a(客户不在库)由数据录入卡建一行 —— 录入卡写的列都得声明 */
|
/* ── 两张类型字典(parts 域,与 P-08 同域;Task 1 已物化 + 播种)── */
|
||||||
{
|
{
|
||||||
domain: 'customer',
|
domain: 'parts',
|
||||||
object: 'profile',
|
object: 'chip_type',
|
||||||
kind: 'master',
|
kind: 'master',
|
||||||
label: '客户档案',
|
label: '芯片类型字典',
|
||||||
fields: [
|
fields: [
|
||||||
col({ name: 'customer_code', type: 'text', label: '客户编码', primaryKey: true }),
|
col({ name: 'type_id', type: 'integer', label: '类型编号', primaryKey: true, auto: true }),
|
||||||
col({ name: 'name', type: 'text', label: '客户名称', required: true }),
|
col({ name: 'type_name', type: 'text', label: '类型名称', required: true }),
|
||||||
col({ name: 'industry', type: 'text', label: '所属行业', expect: true }),
|
col({ name: 'description', type: 'text', label: '类型说明', expect: true }),
|
||||||
col({ name: 'region', type: 'text', label: '区域', expect: true }),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* P-08 芯片主数据:数据工单建整条;标准化参数集已改子表 parts.parameter(2026-08-27,
|
{
|
||||||
用户看了实跑页面指出「必须与目标件结构一致」——目标件本就是两张表,一张主记录一张
|
domain: 'parts',
|
||||||
参数子表,塞进一个文本列违反《统一方案》§六「重复明细用子表承载」) */
|
object: 'type_param',
|
||||||
|
kind: 'master',
|
||||||
|
label: '芯片类型核心参数字段',
|
||||||
|
fields: [
|
||||||
|
col({ name: 'type_id', type: 'integer', 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',
|
domain: 'parts',
|
||||||
object: 'profile',
|
object: 'profile',
|
||||||
/* 数据工单从零建主数据:本单只建库里没有的,在库的料号写下来当场拒(2026-08-27) */
|
|
||||||
createOnly: true,
|
|
||||||
kind: 'master',
|
kind: 'master',
|
||||||
label: '芯片主数据(P-08)',
|
label: '芯片主数据(P-08)',
|
||||||
fields: [
|
fields: [
|
||||||
/*
|
col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true }),
|
||||||
* **只许建本单核过的料号**(2026-08-27 实测):模型给一颗**不存在的** XL2596S-ADJ 铺了空骨架 ——
|
col({ name: 'chip_type_id', type: 'integer', label: '芯片类型', required: true, refersTo: 'parts.chip_type.type_id' }),
|
||||||
* 库里没有、入参里也没有,是把两个候选的名字拼出来的;件上于是多出一行要人填的记录。
|
|
||||||
* 引用核对表:料号必须是这一单(或别单)在 pn_check 里核过的,凭空拼出来的当场拒。
|
|
||||||
*/
|
|
||||||
col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true, refersTo: `${D}.pn_check.internal_pn` }),
|
|
||||||
col({ name: 'brand_name', type: 'text', label: '品牌名称', required: true }),
|
col({ name: 'brand_name', type: 'text', label: '品牌名称', required: true }),
|
||||||
col({ name: 'manufacturer_name', type: 'text', label: '制造商名称', expect: true }),
|
|
||||||
col({ name: 'specification_description', type: 'text', label: '规格描述', required: true }),
|
|
||||||
col({ name: 'package', type: 'text', label: '封装', required: true }),
|
|
||||||
col({ name: 'process_technology', type: 'text', label: '工艺', expect: true }),
|
|
||||||
col({ name: 'lifecycle_status', type: 'text', label: '生命周期状态', required: true, tally: ['active', 'nrnd', 'eol', 'discontinued', 'risk'] }),
|
col({ name: 'lifecycle_status', type: 'text', label: '生命周期状态', required: true, tally: ['active', 'nrnd', 'eol', 'discontinued', 'risk'] }),
|
||||||
col({ name: 'eol_pcn_status', type: 'text', label: 'EOL/PCN 状态', expect: true }),
|
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_ref', type: 'text', label: 'Datasheet 引用', expect: true }),
|
||||||
col({ name: 'datasheet_version', type: 'text', label: 'Datasheet 版本', expect: true }),
|
col({ name: 'datasheet_version', type: 'text', label: 'Datasheet 版本', expect: true }),
|
||||||
col({ name: 'application_scenarios', type: 'text', label: '适配应用场景', expect: true }),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* P-08 子表:标准化参数集,一行一参数,internal_pn 是引用墙(查无那颗主记录当场拒) */
|
/*
|
||||||
|
* P-08 子表:标准化参数集,一行一参数。`createOnly` 撤掉(理由同上一张:在库那颗料的
|
||||||
|
* 现值要能回填进草稿层,只许建新就写不进去)。两处退休(spec §3.2):
|
||||||
|
* `constraint_type` 搬去 `parts.type_param`(同一个参数在两颗料上标法不一致这件事从结构上
|
||||||
|
* 消失);`param_name` 的自指引用墙与 `distinct` 一并退休 —— 它当初是为了硬逼两颗料用同一套
|
||||||
|
* 参数名,而字段清单现在由类型字典给、行由配方按字典铺,名字对不上根本不可能发生。
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
domain: 'parts',
|
domain: 'parts',
|
||||||
object: 'parameter',
|
object: 'parameter',
|
||||||
/* 数据工单从零建主数据:本单只建库里没有的,在库的料号写下来当场拒(2026-08-27) */
|
|
||||||
createOnly: true,
|
|
||||||
kind: 'master',
|
kind: 'master',
|
||||||
label: '标准化参数集(P-08 子表)',
|
label: '标准化参数集(P-08 子表)',
|
||||||
fields: [
|
fields: [
|
||||||
col({ name: 'internal_pn', type: 'text', label: '所属料号', primaryKey: true, refersTo: 'parts.profile.internal_pn' }),
|
col({ name: 'internal_pn', type: 'text', label: '所属料号', primaryKey: true, refersTo: 'parts.profile.internal_pn' }),
|
||||||
/*
|
col({ name: 'param_name', type: 'text', label: '参数名', primaryKey: true }),
|
||||||
* `distinct` 是为了让「两颗料是不是用同一套参数名」变成**基座算得出的数**
|
|
||||||
* (`parts_parameter_distinct_param_name`)—— 见 CMMN 的 s_pns_resolved:
|
|
||||||
* 参数行数 = 不同参数名数 × 建过的料数,只有每颗料的名字集合一模一样时才成立。
|
|
||||||
* 2026-08-27 实测:模型给第二颗料写英文 key(`input_voltage_range`),
|
|
||||||
* 而原 PN 在库里是中文「输入电压范围」—— 名字是主键的一半,对不上就没法逐项比。
|
|
||||||
* 措辞写过两遍都没拦住(设计律第六条:措辞治不了的结构治)。
|
|
||||||
*/
|
|
||||||
col({
|
|
||||||
name: 'param_name',
|
|
||||||
type: 'text',
|
|
||||||
label: '参数名',
|
|
||||||
primaryKey: true,
|
|
||||||
distinct: true,
|
|
||||||
/*
|
|
||||||
* **参数名只能用台账里已经在用的那几个**(自指引用)—— 比对是逐参数对着比的,
|
|
||||||
* 候选身上有一个原 PN 没有的维度,本来也无从比起;名字对不上更是直接判成 UNKNOWN。
|
|
||||||
* 2026-08-27 实测三次:模型给新料写英文 key(`input_voltage_range`),
|
|
||||||
* 而库里既有行是中文「输入电压范围」—— 剧本写过两遍、又加了一条乘法对账
|
|
||||||
* (行数 = 不同名字数 × 建过的料数)仍拦不住**只建一颗**的那一路(5 = 5×1 恒成立)。
|
|
||||||
* 自指是唯一在一颗料时也咬得住的:写下去那一刻基座去这一列里查,查无此值当场拒。
|
|
||||||
* 代价(已知约束):真要引入一个全新的参数维度,得先有一颗料用过它 ——
|
|
||||||
* MVP 的品类词汇表是固定的七项,够用;将来要放开,改成引用一张参数字典表。
|
|
||||||
*/
|
|
||||||
refersTo: 'parts.parameter.param_name',
|
|
||||||
}),
|
|
||||||
col({ name: 'param_value', type: 'text', label: '值', required: true }),
|
col({ name: 'param_value', type: 'text', label: '值', required: true }),
|
||||||
col({ name: 'param_unit', type: 'text', label: '单位', expect: true }),
|
col({ name: 'param_unit', type: 'text', label: '单位', expect: true }),
|
||||||
col({ name: 'constraint_type', type: 'text', label: '约束类型', required: true, tally: ['hard', 'soft'] }),
|
/* 值说明,AI 也读它(spec §3.2) */
|
||||||
col({ name: 'param_note', type: 'text', label: '说明', expect: true }),
|
col({ name: 'param_note', type: 'text', label: '说明', expect: true }),
|
||||||
|
/*
|
||||||
|
* **约束类型不在这里** —— 它长在 `parts.type_param`(按类型标一次)。
|
||||||
|
* 从前每颗料各标一次,同一个参数在两颗料上标法不一致就成了可能;
|
||||||
|
* 而比对是逐参数对着比的,两边约束档不同,判出来的四态就没有意义。
|
||||||
|
*/
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* P-10 原厂技术资料库:数据工单建;评估时只读做证据回链 */
|
/*
|
||||||
|
* ── 活动自持两张(spec §3.3,顶掉退休的 pn_check / input_gap / verdict 三张)──
|
||||||
|
* `sub_eval.part` 是配方唯一能拿来遍历「哪几颗料要建」的数据源(`each: 'mine:sub_eval.part'
|
||||||
|
* where exists_status = absent`)—— 挤进一行案卷里就没法 `where` 过滤,而不能过滤就会给
|
||||||
|
* 已经在库的那颗料也铺一遍空参数行,这是非拆成两张不可的理由。
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
domain: 'parts',
|
domain: D,
|
||||||
object: 'document',
|
object: 'part',
|
||||||
/* 数据工单从零建主数据:本单只建库里没有的,在库的料号写下来当场拒(2026-08-27) */
|
kind: 'record',
|
||||||
createOnly: true,
|
caseKey: 'eval_ref',
|
||||||
kind: 'master',
|
label: '本单两颗料(存在性与类型核对)',
|
||||||
label: '原厂技术资料库(P-10)',
|
|
||||||
fields: [
|
fields: [
|
||||||
col({ name: 'internal_pn', type: 'text', label: '关联内部料号', primaryKey: true }),
|
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
|
||||||
col({ name: 'doc_seq', type: 'text', label: '资料序号(同一料号下 01/02…)', primaryKey: true }),
|
col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true }),
|
||||||
col({ name: 'document_type', type: 'text', label: '资料类型', required: true, tally: ['datasheet', 'app_note', 'reference_design', 'white_paper', 'sample_doc', 'commitment_letter', 'other'] }),
|
col({ name: 'role', type: 'text', label: '角色', required: true, tally: ['original', 'candidate'] }),
|
||||||
col({ name: 'brand_name', type: 'text', label: '品牌名称', expect: true }),
|
/*
|
||||||
col({ name: 'document_title', type: 'text', label: '资料标题', required: true }),
|
* **「在库」是一次 lookup 的结果,不是一句断言**(spec §3.3):判 found 就必须拿得出
|
||||||
col({ name: 'document_version', type: 'text', label: '资料版本', required: true }),
|
* 一个基座查得到的料号,`refersTo` 让查无此值当场拒。`exists_status` 与这一列**每一拍
|
||||||
col({ name: 'document_ref', type: 'text', label: '资料文件链接', required: true }),
|
* 由配方重算**(不设 created 档、不靠谁记账)—— 人把料建好之后自己变 found。
|
||||||
col({ name: 'citation_locator', type: 'text', label: '证据定位(页码/章节/表格)', expect: true }),
|
*/
|
||||||
col({ name: 'document_status', type: 'text', label: '资料状态', required: true, tally: ['valid', 'expired', 'pending', 'void'] }),
|
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',
|
||||||
|
type: 'integer',
|
||||||
|
label: '芯片类型',
|
||||||
|
required: true,
|
||||||
|
/*
|
||||||
|
* **`distinct`**:「两颗料是不是同一个类型」从此是基座数得出来的数
|
||||||
|
* (`sub_eval_part_distinct_chip_type_id`)—— 不同取值恰好 1 种才算一致。
|
||||||
|
* 这一条是 S1 收步与 S2 入场的硬门:两边字段清单不是同一套,逐项比对无从谈起。
|
||||||
|
*/
|
||||||
|
distinct: true,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* P-01 需求主记录:S0 新建 V1,放行时回写五项(current_stage = gate03 那一刻五项必填) */
|
|
||||||
{
|
{
|
||||||
domain: 'demand',
|
domain: D,
|
||||||
object: 'request',
|
object: 'case',
|
||||||
kind: 'master',
|
kind: 'record',
|
||||||
label: '客户需求主记录(P-01)',
|
caseKey: 'eval_ref',
|
||||||
|
label: '本单案卷',
|
||||||
fields: [
|
fields: [
|
||||||
col({ name: 'request_id', type: 'text', label: '需求编号', primaryKey: true }),
|
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
|
||||||
col({ name: 'customer_code', type: 'text', label: '客户编码', required: true, refersTo: 'customer.profile.customer_code' }),
|
col({ name: 'chip_type_id', type: 'integer', label: '本单锁定的芯片类型' }),
|
||||||
col({ name: 'requirement_version', type: 'text', label: '需求版本', required: true }),
|
col({ name: 'gate_reason', type: 'text', label: '入参未就绪原因(件上摆的就是它)' }),
|
||||||
col({ name: 'request_type', type: 'text', label: '需求类型', required: true }),
|
col({ name: 'summary', type: 'text', label: '结论摘要' }),
|
||||||
col({ name: 'request_description', type: 'text', label: '需求描述', required: true }),
|
|
||||||
col({ name: 'input_snapshot_ref', type: 'text', label: '输入快照', required: true }),
|
|
||||||
col({ name: 'original_internal_pn', type: 'text', label: '原内部料号', required: true }),
|
|
||||||
col({ name: 'bom_position', type: 'text', label: 'BOM 位', expect: true }),
|
|
||||||
col({ name: 'application_scenario', type: 'text', label: '应用场景', expect: true }),
|
|
||||||
col({ name: 'technical_constraints', type: 'text', label: '技术约束' }),
|
|
||||||
col({ name: 'risk_level', type: 'text', label: '风险等级', tally: ['low', 'medium', 'high', 'critical'], requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
|
||||||
col({ name: 'verification_requirement', type: 'text', label: '验证/复测/确认要求', requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
|
||||||
col({ name: 'request_status', type: 'text', label: '需求状态', required: true, tally: ['pending', 'responded', 'won', 'abandoned', 'closed'] }),
|
|
||||||
col({ name: 'current_activity', type: 'text', label: '当前 Activity', requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
|
||||||
col({ name: 'current_stage', type: 'text', label: '当前阶段' }),
|
|
||||||
col({ name: 'next_action', type: 'text', label: '下一步动作', requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
|
||||||
col({ name: 'output_ref', type: 'text', label: '标准输出链接', requiredWhen: { column: 'current_stage', value: 'gate03' } }),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* P-11 替代关系主记录:一候选一条;两枚 PN 都是引用墙(查无此料当场拒 —— 存在性检查的硬门) */
|
/*
|
||||||
|
* P-11 替代关系主记录 —— 一单一行。退休三列(spec §3.4):`substitution_type`(新流程里没有
|
||||||
|
* 它的定夺人)、`confidence_score`、`analysis_report_ref`;`recommendation_level` 的
|
||||||
|
* `alternate` 档一并退休(原 D02 的 configurable 来路已不存在,只剩三档)。
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
domain: 'substitution',
|
domain: 'substitution',
|
||||||
object: 'relation',
|
object: 'relation',
|
||||||
@@ -432,24 +351,24 @@ col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true
|
|||||||
fields: [
|
fields: [
|
||||||
col({ name: 'substitution_relation_id', type: 'text', label: '替代关系ID', primaryKey: true }),
|
col({ name: 'substitution_relation_id', type: 'text', label: '替代关系ID', primaryKey: true }),
|
||||||
col({ name: 'eval_ref', type: 'text', label: '评估案号', required: true }),
|
col({ name: 'eval_ref', type: 'text', label: '评估案号', required: true }),
|
||||||
col({ name: 'request_id', type: 'text', label: '关联需求编号', required: true }),
|
|
||||||
col({ name: 'bom_position', type: 'text', label: '关联 BOM 位', expect: true }),
|
|
||||||
col({ name: 'original_internal_pn', type: 'text', label: '原内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
|
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' }),
|
col({ name: 'candidate_internal_pn', type: 'text', label: '候选内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
|
||||||
col({ name: 'substitution_type', type: 'text', label: '替代类型', required: true, tally: ['pin_to_pin', 'functional', 'parametric', 'other'] }),
|
col({ name: 'chip_type_id', type: 'integer', label: '芯片类型', required: true }),
|
||||||
col({ name: 'recommendation_level', type: 'text', label: '推荐等级', required: true, tally: ['recommended', 'conditional', 'alternate', 'not_recommended'] }),
|
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: 'recommendation_reason', type: 'text', label: '推荐理由', required: true }),
|
||||||
col({ name: 'difference_summary', 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', 'critical'] }),
|
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: 'verification_requirement', type: 'text', label: '验证要求', requiredWhen: { column: 'recommendation_level', value: 'conditional' } }),
|
||||||
col({ name: 'validation_id', type: 'text', label: '关联验证ID' }),
|
col({ name: 'validation_id', type: 'text', label: '关联验证ID' }),
|
||||||
col({ name: 'analysis_status', type: 'text', label: '分析状态', required: true, tally: ['ai_draft', 'fae_confirmed', 'pending_customer', 'customer_approved', 'rejected', 'void'] }),
|
/* 固定 fae_confirmed —— 分析状态与客户批准状态分离,本活动只写这一层 */
|
||||||
col({ name: 'analysis_report_ref', type: 'text', label: '替代分析报告', expect: true }),
|
col({ name: 'analysis_status', type: 'text', label: '分析状态', required: true }),
|
||||||
col({ name: 'confidence_score', type: 'number', label: '证据可信度', expect: true }),
|
|
||||||
col({ name: 'rule_version', type: 'text', label: '差异规则版本', required: true }),
|
col({ name: 'rule_version', type: 'text', label: '差异规则版本', required: true }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* P-11 差异明细子表:一条参数一条记录;relation id 上 distinct 让基座数「几条关系有明细」 */
|
/*
|
||||||
|
* P-11 差异明细子表 —— 一参数一行。`evidence_ref` 改名 `evidence_note`「判断依据」:
|
||||||
|
* 不写 P-10 了,AI 能给的就是依据不是文件引用,名字得说实话(spec §3.4)。
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
domain: 'substitution',
|
domain: 'substitution',
|
||||||
object: 'difference_item',
|
object: 'difference_item',
|
||||||
@@ -459,21 +378,28 @@ col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true
|
|||||||
col({ name: 'substitution_relation_id', type: 'text', label: '替代关系ID', primaryKey: true, distinct: true, refersTo: 'substitution.relation.substitution_relation_id' }),
|
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: 'difference_item_id', type: 'text', label: '差异明细ID', primaryKey: true }),
|
||||||
col({ name: 'parameter_name', type: 'text', label: '标准参数名', required: 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: 'original_value', type: 'text', label: '原物料参数值', required: true }),
|
||||||
col({ name: 'candidate_value', type: 'text', label: '候选物料参数值', required: true }),
|
col({ name: 'candidate_value', type: 'text', label: '候选物料参数值', required: true }),
|
||||||
col({ name: 'parameter_unit', type: 'text', label: '参数单位', expect: true }),
|
col({ name: 'parameter_unit', type: 'text', label: '参数单位', expect: true }),
|
||||||
col({ name: 'constraint_type', type: 'text', label: '约束类型', required: true, tally: ['hard', 'soft'] }),
|
/* AI 判:逐参数比对,S2 唯一一次 analyze 出场的出参之一 */
|
||||||
col({ name: 'compare_result', type: 'text', label: '比对结果(工具出参)', expect: true }),
|
|
||||||
col({ name: 'difference_status', type: 'text', label: '差异状态', required: true, tally: ['match', 'mismatch', 'unknown', 'conditional'] }),
|
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: '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: '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: 'risk_level', type: 'text', label: '风险等级', required: true, tally: ['low', 'medium', 'high'] }),
|
||||||
col({ name: 'evidence_ref', type: 'text', label: '差异证据引用', required: true }),
|
|
||||||
col({ name: 'confidence', type: 'number', label: 'AI 置信度', expect: true }),
|
|
||||||
col({ name: 'rule_version', type: 'text', label: '差异规则版本', required: true }),
|
col({ name: 'rule_version', type: 'text', label: '差异规则版本', required: true }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* P-06 验证记录:事实类 —— 本活动只写「验证要求」(状态 not_started),不经数据工单建 */
|
/* P-06 验证记录:一单一行,关联的是本单案号(P-01 不在本活动里,顶掉 request_id) */
|
||||||
{
|
{
|
||||||
domain: 'validation',
|
domain: 'validation',
|
||||||
object: 'record',
|
object: 'record',
|
||||||
@@ -481,83 +407,13 @@ col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true
|
|||||||
label: '芯片验证记录(P-06)',
|
label: '芯片验证记录(P-06)',
|
||||||
fields: [
|
fields: [
|
||||||
col({ name: 'validation_id', type: 'text', label: '验证ID', primaryKey: true }),
|
col({ name: 'validation_id', type: 'text', label: '验证ID', primaryKey: true }),
|
||||||
col({ name: 'request_id', type: 'text', label: '关联需求编号', required: 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_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'] }),
|
col({ name: 'validation_type', type: 'text', label: '验证类型', required: true, tally: ['pin_to_pin', 'functional', 'reliability', 'certification', 'retest', 'parametric'] }),
|
||||||
col({ name: 'validation_status', type: 'text', label: '验证状态', required: true, tally: ['not_started', 'in_progress', 'passed', 'failed', 'conditional', 'pending_evidence'] }),
|
/* 固定 not_started */
|
||||||
|
col({ name: 'validation_status', type: 'text', label: '验证状态', required: true }),
|
||||||
col({ name: 'test_condition', type: 'text', label: '测试条件', expect: true }),
|
col({ name: 'test_condition', type: 'text', label: '测试条件', expect: true }),
|
||||||
col({ name: 'next_validation_action', type: 'text', label: '后续验证动作', required: true }),
|
col({ name: 'next_validation_action', type: 'text', label: '后续验证动作', required: true }),
|
||||||
col({ name: 'evidence_ref', type: 'text', label: '验证证据引用', expect: true }),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
/* ── 活动自持单据(record,caseKey eval_ref)── */
|
|
||||||
{
|
|
||||||
domain: D,
|
|
||||||
object: 'pn_check',
|
|
||||||
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'] }),
|
|
||||||
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: '备注' }),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
domain: D,
|
|
||||||
object: 'input_gap',
|
|
||||||
kind: 'record',
|
|
||||||
caseKey: 'eval_ref',
|
|
||||||
label: '评估输入清单',
|
|
||||||
fields: [
|
|
||||||
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
|
|
||||||
col({ name: 'item_key', type: 'text', label: '输入项键', primaryKey: true }),
|
|
||||||
col({ name: 'input_item', type: 'text', label: '输入项', required: true }),
|
|
||||||
col({ name: 'source_ledger', type: 'text', label: '来源台账', expect: true }),
|
|
||||||
col({ name: 'status', type: 'text', label: '状态', required: true, tally: ['gap', 'default', 'ok'] }),
|
|
||||||
col({ name: 'detail', type: 'text', label: '绑定值 / 缺口说明', expect: true }),
|
|
||||||
col({ name: 'supplement', type: 'text', label: '补充内容', requiredWhen: { column: 'status', value: 'gap' } }),
|
|
||||||
col({ name: 'origin', type: 'text', label: '来源(ai/human-added)' }),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
domain: D,
|
|
||||||
object: 'verdict',
|
|
||||||
kind: 'record',
|
|
||||||
caseKey: 'eval_ref',
|
|
||||||
label: '本次评估结论',
|
|
||||||
fields: [
|
|
||||||
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
|
|
||||||
col({ name: 'request_id', type: 'text', label: '需求编号' }),
|
|
||||||
col({ name: 'rule_version', type: 'text', label: '规则版本' }),
|
|
||||||
col({ name: 'original_internal_pn', type: 'text', label: '原内部料号' }),
|
|
||||||
col({ name: 'candidate_total', type: 'integer', label: '候选数' }),
|
|
||||||
col({ name: 'input_confirmed_total', type: 'integer', label: '评估输入已确认' }),
|
|
||||||
col({ name: 'review_approved_total', type: 'integer', label: '差异结论已审核通过' }),
|
|
||||||
col({ name: 'landing_prepared_total', type: 'integer', label: '拟落账已备好' }),
|
|
||||||
col({ name: 'relation_total', type: 'integer', label: '替代关系条数' }),
|
|
||||||
col({ name: 'recommended_total', type: 'integer', label: '推荐' }),
|
|
||||||
col({ name: 'conditional_total', type: 'integer', label: '条件推荐' }),
|
|
||||||
col({ name: 'alternate_total', type: 'integer', label: '备选' }),
|
|
||||||
col({ name: 'not_recommended_total', type: 'integer', label: '不推荐' }),
|
|
||||||
col({ name: 'summary', type: 'text', label: '本次评估结论' }),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -572,33 +428,29 @@ col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true
|
|||||||
|
|
||||||
/* ── 资产初始版 ──────────────────────────────────────────────── */
|
/* ── 资产初始版 ──────────────────────────────────────────────── */
|
||||||
assets: {
|
assets: {
|
||||||
businessModel: { cmmnXml: CMMN_XML, narrative: NARRATIVE, dmn: ASSESSMENT_DMN, steps: ASSESSMENT_STEPS, phases: ASSESSMENT_PHASES },
|
|
||||||
skills: ASSESSMENT_SKILLS,
|
|
||||||
/*
|
/*
|
||||||
* 五个局面各一张:
|
* DMN 判定表已退役:D01(逐参数四态)改由 S2 唯一一次 analyze 判定;D02(推荐等级)的规则
|
||||||
* · `p08-master-create` —— S0d 摆,数据工单(P-08),件上可加行、可编辑;
|
* 搬进配方的 when 链(spec §3.5)—— 规则只许有一处真源,再留一份 DMN 就是同一件事两处
|
||||||
* · `p10-document-create` —— S0d 摆,数据工单(P-10),同上;
|
* 各存一份。Task 3 会把 `steps` 换成四步、`guide` 全部清空(auto 配方接管行为)。
|
||||||
* · `eval-input-list` —— S1 摆,评估输入清单:绑定区只读 + 缺口表可编辑;
|
|
||||||
* · `difference-review` —— S2/S3/S4 摆,差异明细 + 各候选推荐等级,只读;
|
|
||||||
* · `landing-confirm` —— S5/T_release 摆,拟写 P-11 / 拟回写 P-01,只读;放行后即归档件。
|
|
||||||
*/
|
*/
|
||||||
viewTemplates: [
|
businessModel: { cmmnXml: CMMN_XML, narrative: NARRATIVE, dmn: { drd: [], tables: [] }, steps: ASSESSMENT_STEPS, phases: ASSESSMENT_PHASES },
|
||||||
{ name: 'p08-master-create', html: P08_MASTER_CREATE },
|
/* 三条技能已随重写退役(全程无 agent,没有临场编排可言);skills.ts 的删除排在 Task 10 */
|
||||||
{ name: 'p10-document-create', html: P10_DOCUMENT_CREATE },
|
skills: [],
|
||||||
{ name: 'eval-input-list', html: EVAL_INPUT_LIST },
|
/*
|
||||||
{ name: 'difference-review', html: DIFFERENCE_REVIEW },
|
* 五张旧局面全部作废(它们绑的九步与旧台账地址已经不存在),**暂时只留运营中心那张看板** ——
|
||||||
{ name: 'landing-confirm', html: LANDING_CONFIRM },
|
* 三张新件(`input-supplement` / `difference-review` / `landing-confirm`)由 Task 7–9 各自加回;
|
||||||
/* 运营中心 L2 的沙箱视图(2026-08-27,用户定替换掉全流程分支图):关键台账数据看板 —— 最近一次已放行
|
* 中途(Task 2–Task 9 之间)包不装,不会被静态审拦下。看板自己的 `data` 声明现在也引着已经
|
||||||
实例的落账快照,行由基座按 `data` 声明从正式表圈;无脚本,报高脚本由服务端出口注入 */
|
* 不存在的台账地址(`sub_eval.pn_check` 等)——Task 9「运营中心看板重绑」会把它换成新九张表。
|
||||||
{ name: 'activity-ledger', html: LEDGER_BOARD, data: LEDGER_BOARD_DATA },
|
*/
|
||||||
],
|
viewTemplates: [{ name: 'activity-ledger', html: LEDGER_BOARD, data: LEDGER_BOARD_DATA }],
|
||||||
cardSchemas: {},
|
cardSchemas: {},
|
||||||
distillHooks: [{ on: 'terminal', prompt: '归纳本次差异评估:哪些候选、各自推荐等级与关键依据、UNKNOWN 挂了什么验证任务、审核是否被驳回过(为什么)、数据工单建了哪些主数据。' }],
|
/* 蒸馏钩子撤空:它是给 agent 在终态回看轨迹用的,而这个活动全程无 agent(AI 只出场一次、答一份结构化判断,没有可供归纳的多轮轨迹) */
|
||||||
|
distillHooks: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ── 样例集:五条形状真不同(常备案例即验收案例)──
|
* ── 样例集:五条形状真不同(常备案例即验收案例)──
|
||||||
* 第一条满载且走「候选缺库 → 数据工单」那条最长的路(测试台落地即预填的是它);样例名 ≤14 字。
|
* 本任务不改样例(它们仍是旧九步 / 旧入参形状的素材,Task 3 重写案例模型时一并核对)。
|
||||||
* 库里只播了 LM2596S-5.0 / LM2596S-ADJ 两颗(见 seed-mvp-data.ts),XL2596S-5.0 / CN-DCDC-32V / LM2596S-3.3 刻意不在库。
|
* 库里只播了 LM2596S-5.0 / LM2596S-ADJ 两颗(见 seed-mvp-data.ts),XL2596S-5.0 / CN-DCDC-32V / LM2596S-3.3 刻意不在库。
|
||||||
*/
|
*/
|
||||||
samples: [
|
samples: [
|
||||||
|
|||||||
Reference in New Issue
Block a user