feat(w05): 主数据基线加料件/需求/替代/验证四域六表(P-08/P-10/P-01/P-11/P-06)与样例料号

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
xcs
2026-08-26 23:10:08 +08:00
co-authored by Claude Fable 5
parent a681ffe9f7
commit 63034f5ab2
3 changed files with 230 additions and 9 deletions
@@ -6,19 +6,36 @@
* 2026-07-31 重分:单据归各活动自己的域,这个文件只留**主数据**,按常规 CRM / ERP 分四域。
*
* 守的是三条,每条都能机检:
* · 只有这四个域,不多不少 —— 多一个就说明有人把单据塞进主数据了;
* · 八个域,多一个就说明有人把单据塞进主数据了;
* · 每张表都是 `master` 且**不带单标识** —— 带了就是单据归错类(安装静态审也会拦);
* · 主键是**业务实体**,不是单。
*/
import { describe, expect, it } from 'vitest';
import { MASTER_DOMAINS, MASTER_OBJECTS } from '../seed-mvp-data.ts';
import { MASTER_DOMAINS, MASTER_OBJECTS, MASTER_SAMPLE_ROWS } from '../seed-mvp-data.ts';
describe('主数据四域', () => {
it('只有客户 / 项目 / 供应商 / 市场四个域', () => {
expect([...MASTER_DOMAINS]).toEqual(['customer', 'project', 'supplier', 'market']);
it('八个域:客户 / 项目 / 供应商 / 市场 + 料件 / 需求 / 替代 / 验证(SS02 差异评估 2026-08-26 加后四域)', () => {
expect([...MASTER_DOMAINS]).toEqual(['customer', 'project', 'supplier', 'market', 'parts', 'demand', 'substitution', 'validation']);
expect([...new Set(MASTER_OBJECTS.map((o) => o.domain))].sort()).toEqual([...MASTER_DOMAINS].sort());
});
it('SS02 差异评估要读写的六张主数据表都在基线里,主键照《统一方案》', () => {
const pk = (domain: string, object: string) =>
MASTER_OBJECTS.find((o) => o.domain === domain && o.object === object)?.fields.filter((f) => f.primaryKey).map((f) => f.name);
expect(pk('parts', 'profile')).toEqual(['internal_pn']);
expect(pk('parts', 'document')).toEqual(['internal_pn', 'doc_seq']);
expect(pk('demand', 'request')).toEqual(['request_id']);
expect(pk('substitution', 'relation')).toEqual(['substitution_relation_id']);
expect(pk('substitution', 'difference_item')).toEqual(['substitution_relation_id', 'difference_item_id']);
expect(pk('validation', 'record')).toEqual(['validation_id']);
});
it('样例料号:原 PN 与同族可调版在库,候选 XL2596S-5.0 刻意不播(数据工单那条路要试得出来)', () => {
const parts = MASTER_SAMPLE_ROWS.filter((r) => r.address.domain === 'parts' && r.address.object === 'profile').map((r) => r.key['internal_pn']);
expect(parts).toEqual(['LM2596S-5.0', 'LM2596S-ADJ']);
expect(parts).not.toContain('XL2596S-5.0');
});
it('四个域各自都有表 —— 空占位也得立得住', () => {
for (const d of MASTER_DOMAINS) {
expect(MASTER_OBJECTS.filter((o) => o.domain === d).length, `${d} 域一张表都没有`).toBeGreaterThan(0);
+1 -1
View File
@@ -140,5 +140,5 @@ export async function installRealActivities(container: Container): Promise<void>
cause: { kind: 'governance', ref: 'seed:master-sample' },
});
}
log(` 样例客户已就位:${MASTER_SAMPLE_ROWS[1]?.values.email ?? ''}(样例 2 用它试老客户那条路)`);
log(` 样例主数据已就位:${MASTER_SAMPLE_ROWS.length} 行(客户 / 项目 / 联系人 / 料号)`);
}
+208 -4
View File
@@ -19,7 +19,7 @@
* 表建出来但一行数据都没有 —— 治理台「主数据」那一区能看见它们等着被接上。
*/
import { objectDeclarationSchema } from '@contracts';
import type { LedgerFieldType, ObjectDeclaration } from '@contracts';
import type { LedgerAddress, LedgerFieldType, LedgerKey, LedgerRow, ObjectDeclaration } from '@contracts';
const f = (name: string, type: LedgerFieldType, label: string, primaryKey = false) => ({
name,
@@ -142,10 +142,127 @@ export const MASTER_OBJECTS: readonly ObjectDeclaration[] = [
f('stock', 'integer', '可见库存'),
f('trend', 'text', '走势'),
]),
/* ── 料件:P-08 芯片主数据 / P-10 原厂技术资料库(SS02 差异评估 2026-08-26 加)──
字段名与《SS02-SS03-SS04_Demo台账字段细化与统一方案》§4.2 一字不差;全链路只认 internal_pn。
参数集是 P-08 的一个字段(用户定:一张表,不建子表),一行一参数:
`参数名 | 值 | 单位 | hard/soft | 说明`。老的 market.part(主键 pn)不复用,记 debt 待并。 */
master('parts', 'profile', '芯片主数据(P-08)', [
f('internal_pn', 'text', '内部料号', true),
f('customer_pn', 'text', '客户料号'),
f('brand_name', 'text', '品牌名称'),
f('manufacturer_name', 'text', '制造商名称'),
f('specification_description', 'text', '规格描述'),
f('normalized_parameter_set', 'text', '标准化参数集(一行一参数:参数名 | 值 | 单位 | hard/soft | 说明)'),
f('package', 'text', '封装'),
f('process_technology', 'text', '工艺'),
f('lifecycle_status', 'text', '生命周期状态'),
f('eol_pcn_status', 'text', 'EOL/PCN 状态'),
f('datasheet_ref', 'text', 'Datasheet 引用'),
f('datasheet_version', 'text', 'Datasheet 版本'),
f('application_scenarios', 'text', '适配应用场景'),
f('pin_to_pin_relation_ref', 'text', 'Pin-to-Pin 关系引用'),
]),
master('parts', 'document', '原厂技术资料库(P-10)', [
f('internal_pn', 'text', '关联内部料号', true),
f('doc_seq', 'text', '资料序号(同一料号下 01/02…)', true),
f('document_type', 'text', '资料类型'),
f('brand_name', 'text', '品牌名称'),
f('document_title', 'text', '资料标题'),
f('document_version', 'text', '资料版本'),
f('document_date', 'date', '资料发布日期'),
f('document_ref', 'text', '资料文件链接'),
f('citation_locator', 'text', '证据定位(页码/章节/表格)'),
f('source_contact_ref', 'text', '原厂支持联系人'),
f('document_status', 'text', '资料状态'),
]),
/* ── 需求:P-01 客户具体需求与项目任务主台账 ── */
master('demand', 'request', '客户需求主记录(P-01)', [
f('request_id', 'text', '需求编号', true),
f('customer_code', 'text', '客户编码'),
f('project_code', 'text', '项目编码'),
f('requirement_version', 'text', '需求版本'),
f('request_type', 'text', '需求类型'),
f('request_description', 'text', '需求描述'),
f('input_snapshot_ref', 'text', '输入快照'),
f('bom_id', 'text', 'BOM ID'),
f('bom_position', 'text', 'BOM 位'),
f('original_internal_pn', 'text', '原内部料号'),
f('customer_pn', 'text', '客户料号'),
f('application_scenario', 'text', '应用场景'),
f('technical_constraints', 'text', '技术约束'),
f('risk_level', 'text', '风险等级'),
f('verification_requirement', 'text', '验证/复测/确认要求'),
f('request_status', 'text', '需求状态'),
f('current_activity', 'text', '当前 Activity'),
f('current_stage', 'text', '当前阶段'),
f('next_action', 'text', '下一步动作'),
f('output_ref', 'text', '标准输出链接'),
]),
/* ── 替代:P-11 替代选型与批准关系库 —— 主记录 + 差异明细子表(《统一方案》§六:重复明细用子表)── */
master('substitution', 'relation', '替代关系主记录(P-11)', [
f('substitution_relation_id', 'text', '替代关系ID', true),
f('request_id', 'text', '关联需求编号'),
f('project_code', 'text', '关联项目编码'),
f('bom_position', 'text', '关联 BOM 位'),
f('original_internal_pn', 'text', '原内部料号'),
f('candidate_internal_pn', 'text', '候选内部料号'),
f('substitution_type', 'text', '替代类型'),
f('recommendation_level', 'text', '推荐等级'),
f('recommendation_reason', 'text', '推荐理由'),
f('difference_summary', 'text', '差异摘要'),
f('risk_level', 'text', '替代风险等级'),
f('verification_requirement', 'text', '验证要求'),
f('validation_id', 'text', '关联验证ID'),
f('analysis_status', 'text', '分析状态'),
/* 客户批准三列由独立审批流维护 —— 基线建列,差异评估活动**不声明写权** */
f('customer_approval_status', 'text', '客户批准状态'),
f('customer_approval_at', 'timestamp', '客户批准时间'),
f('customer_approval_ref', 'text', '客户批准凭证'),
f('customer_feedback', 'text', '客户反馈'),
f('analysis_report_ref', 'text', '替代分析报告'),
f('confidence_score', 'number', '证据可信度'),
f('rule_version', 'text', '差异规则版本'),
]),
master('substitution', 'difference_item', '差异明细(P-11 子表)', [
f('substitution_relation_id', 'text', '替代关系ID', true),
f('difference_item_id', 'text', '差异明细ID', true),
f('parameter_name', 'text', '标准参数名'),
f('original_value', 'text', '原物料参数值'),
f('candidate_value', 'text', '候选物料参数值'),
f('parameter_unit', 'text', '参数单位'),
f('constraint_type', 'text', '约束类型'),
f('difference_status', 'text', '差异状态'),
f('impact_statement', 'text', '影响说明'),
f('risk_level', 'text', '风险等级'),
f('evidence_ref', 'text', '差异证据引用'),
f('confidence', 'number', 'AI 置信度'),
f('rule_version', 'text', '差异规则版本'),
]),
/* ── 验证:P-06 样品交付与芯片验证记录(事实类:只能实测产生,数据工单不许建)── */
master('validation', 'record', '芯片验证记录(P-06)', [
f('validation_id', 'text', '验证ID', true),
f('request_id', 'text', '关联需求编号'),
f('customer_code', 'text', '客户编码'),
f('validation_internal_pn', 'text', '验证内部料号'),
f('validation_type', 'text', '验证类型'),
f('test_condition', 'text', '测试条件'),
f('test_parameters', 'text', '测试参数'),
f('test_result', 'text', '测试结果'),
f('validation_status', 'text', '验证状态'),
f('issue_risk', 'text', '问题与风险'),
f('sample_version', 'text', '样品版本'),
f('sample_lot', 'text', '样品批次'),
f('evidence_ref', 'text', '验证证据引用'),
f('next_validation_action', 'text', '后续验证动作'),
]),
];
/** 四域的呈现次序 —— 谁在买 → 为什么买 → 从哪买 → 买什么 */
export const MASTER_DOMAINS = ['customer', 'project', 'supplier', 'market'] as const;
/** 八域的呈现次序 —— 谁在买 → 为什么买 → 从哪买 → 买什么 → 料件 → 需求 → 替代 → 验证 */
export const MASTER_DOMAINS = ['customer', 'project', 'supplier', 'market', 'parts', 'demand', 'substitution', 'validation'] as const;
/**
* **样例客户** —— 让「老客户」那条路试得出来。
@@ -158,7 +275,7 @@ export const MASTER_DOMAINS = ['customer', 'project', 'supplier', 'market'] as c
* 只播这一位,且是**幂等的按主键写**:它是让样例跑得起来的最小夹具,
* 不是往主数据里塞演示数据 —— 真实客户由 A1 建档时一位一位落进来。
*/
export const MASTER_SAMPLE_ROWS = [
export const MASTER_SAMPLE_ROWS: readonly { address: LedgerAddress; key: LedgerKey; values: LedgerRow }[] = [
/*
* **Itelma** —— 老客户测试案例的那一位(照 `JCAI_PRD/docs/活动执行页/` 两份目标件)。
* 俄罗斯车规电子、项目制;客户码 CU-0142 就是需求件上写的「已命中客户资料库」那一条。
@@ -344,4 +461,91 @@ export const MASTER_SAMPLE_ROWS = [
review_note: '',
},
},
/*
* ── SS02 差异评估的样例料号(2026-08-26)──
* 只播**原 PN**与**同族可调版**两颗:「都在库」那条路靠它们;候选 XL2596S-5.0 与国产 DCDC
* **刻意不播** —— 「候选缺库 → 派数据工单」「硬约束不推荐」两条路要试得出来,库里得真没有。
* 参数集一行一参数(参数名 | 值 | 单位 | hard/soft | 说明),与 P-08 声明的行语法同一份。
*/
{
address: { domain: 'parts', object: 'profile' },
key: { internal_pn: 'LM2596S-5.0' },
values: {
internal_pn: 'LM2596S-5.0',
brand_name: 'TI',
manufacturer_name: 'Texas Instruments',
specification_description: '降压型 DCDC 稳压器,3A,固定 5.0V,150kHz',
normalized_parameter_set: [
'输入电压范围 | 4.5~40 | V | hard | 客户输入上限 40V',
'输出电压 | 5.0 | V | hard | 固定版',
'输出电流 | 3 | A | hard | 满载能力',
'开关频率 | 150 | kHz | hard | 影响外围电感/电容',
'静态电流 | 5 | mA | soft | 待机功耗',
'工作温度 | -40~+85 | ℃ | hard | 工业温域',
'使能阈值 | 1.4 | V | hard | ON/OFF 引脚阈值',
].join('\n'),
package: 'TO-263-5',
process_technology: 'BCD',
lifecycle_status: 'nrnd',
eol_pcn_status: 'PCN-2026-07:转 NRND,2027-01 起不接新设计',
datasheet_ref: 'https://www.ti.com/lit/ds/symlink/lm2596.pdf',
datasheet_version: 'Rev H (2023-09)',
application_scenarios: '工业电源;消费电源',
},
},
{
address: { domain: 'parts', object: 'profile' },
key: { internal_pn: 'LM2596S-ADJ' },
values: {
internal_pn: 'LM2596S-ADJ',
brand_name: 'TI',
manufacturer_name: 'Texas Instruments',
specification_description: '降压型 DCDC 稳压器,3A,可调输出 1.23~37V,150kHz(LM2596 同族)',
normalized_parameter_set: [
'输入电压范围 | 4.5~40 | V | hard | 与固定版同',
'输出电压 | 1.23~37 | V | hard | 可调版,靠外围反馈网络定',
'输出电流 | 3 | A | hard | 满载能力',
'开关频率 | 150 | kHz | hard | 影响外围电感/电容',
'静态电流 | 5 | mA | soft | 待机功耗',
'工作温度 | -40~+85 | ℃ | hard | 工业温域',
'使能阈值 | 1.4 | V | hard | ON/OFF 引脚阈值',
].join('\n'),
package: 'TO-263-5',
process_technology: 'BCD',
lifecycle_status: 'active',
datasheet_ref: 'https://www.ti.com/lit/ds/symlink/lm2596.pdf',
datasheet_version: 'Rev H (2023-09)',
application_scenarios: '工业电源;消费电源',
},
},
{
address: { domain: 'parts', object: 'document' },
key: { internal_pn: 'LM2596S-5.0', doc_seq: '01' },
values: {
internal_pn: 'LM2596S-5.0',
doc_seq: '01',
document_type: 'datasheet',
brand_name: 'TI',
document_title: 'LM2596 SIMPLE SWITCHER Power Converter 150-kHz 3-A Step-Down Voltage Regulator',
document_version: 'Rev H (2023-09)',
document_ref: 'https://www.ti.com/lit/ds/symlink/lm2596.pdf',
citation_locator: '§6.5 Electrical Characteristics 表 / §6.3 Recommended Operating Conditions',
document_status: 'valid',
},
},
{
address: { domain: 'parts', object: 'document' },
key: { internal_pn: 'LM2596S-ADJ', doc_seq: '01' },
values: {
internal_pn: 'LM2596S-ADJ',
doc_seq: '01',
document_type: 'datasheet',
brand_name: 'TI',
document_title: 'LM2596 SIMPLE SWITCHER Power Converter 150-kHz 3-A Step-Down Voltage Regulator',
document_version: 'Rev H (2023-09)',
document_ref: 'https://www.ti.com/lit/ds/symlink/lm2596.pdf',
citation_locator: '§6.5 Electrical Characteristics 表(ADJ 列)',
document_status: 'valid',
},
},
] as const;