From 63034f5ab2957112a4a29cbeb14fddcc92d8aa86 Mon Sep 17 00:00:00 2001 From: xcs Date: Wed, 26 Aug 2026 23:10:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(w05):=20=E4=B8=BB=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=9F=BA=E7=BA=BF=E5=8A=A0=E6=96=99=E4=BB=B6/=E9=9C=80?= =?UTF-8?q?=E6=B1=82/=E6=9B=BF=E4=BB=A3/=E9=AA=8C=E8=AF=81=E5=9B=9B?= =?UTF-8?q?=E5=9F=9F=E5=85=AD=E8=A1=A8(P-08/P-10/P-01/P-11/P-06)=E4=B8=8E?= =?UTF-8?q?=E6=A0=B7=E4=BE=8B=E6=96=99=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh --- .../src/app/__tests__/seed-mvp-data.test.ts | 25 ++- apps/api/src/app/seed-activities.ts | 2 +- apps/api/src/app/seed-mvp-data.ts | 212 +++++++++++++++++- 3 files changed, 230 insertions(+), 9 deletions(-) diff --git a/apps/api/src/app/__tests__/seed-mvp-data.test.ts b/apps/api/src/app/__tests__/seed-mvp-data.test.ts index c854677..1b326d0 100644 --- a/apps/api/src/app/__tests__/seed-mvp-data.test.ts +++ b/apps/api/src/app/__tests__/seed-mvp-data.test.ts @@ -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); diff --git a/apps/api/src/app/seed-activities.ts b/apps/api/src/app/seed-activities.ts index 5d6a417..03e9dab 100644 --- a/apps/api/src/app/seed-activities.ts +++ b/apps/api/src/app/seed-activities.ts @@ -140,5 +140,5 @@ export async function installRealActivities(container: Container): Promise cause: { kind: 'governance', ref: 'seed:master-sample' }, }); } - log(` 样例客户已就位:${MASTER_SAMPLE_ROWS[1]?.values.email ?? ''}(样例 2 用它试老客户那条路)`); + log(` 样例主数据已就位:${MASTER_SAMPLE_ROWS.length} 行(客户 / 项目 / 联系人 / 料号)`); } diff --git a/apps/api/src/app/seed-mvp-data.ts b/apps/api/src/app/seed-mvp-data.ts index 249fe89..5bb8d2a 100644 --- a/apps/api/src/app/seed-mvp-data.ts +++ b/apps/api/src/app/seed-mvp-data.ts @@ -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;