/** * 主数据四域 —— **客户 / 项目 / 供应商 / 市场**(2026-07-31 用户定,照常规 CRM & ERP 的分法)。 * * 为什么主数据要单独立域、由**平台**播种,而不是跟着某个活动走: * * 台账原来只按「谁写的」分域(`quote_intake.*` / `quote_alternative.*` …)。那是**实现轴**: * 六个活动各有一张 verdict,一单的故事散在六处;而真正需要治理的那几张 * (客户档案、历史价)被挤进一个叫 `shared` 的抽屉 —— 那个名字描述的是机制(跨活动共享), * 不是性质(主数据)。用机制命名,抽屉必然变杂物间。 * * 更要紧的是**所有权**:主数据不是任何一个活动的产出。客户档案先于活动存在、 * 活动退役了它还在;活动只是**读它做判断、提议更新它**。 * 所以它由平台播种物化,不挂在谁的清单下 —— 活动装了卸了,主数据都在。 * * 与三类性质的关系(见 `ledgerObjectKindSchema`):这里全是 `master`。 * 单据(`record`)归各活动自己的域;派生指标(`derived`)另说 —— 它本不该手写。 * * **现在是空占位**:结构先立起来,还没有活动往里写。 * 表建出来但一行数据都没有 —— 治理台「主数据」那一区能看见它们等着被接上。 */ import { objectDeclarationSchema } from '@contracts'; import type { LedgerAddress, LedgerFieldType, LedgerKey, LedgerRow, ObjectDeclaration } from '@contracts'; const f = (name: string, type: LedgerFieldType, label: string, primaryKey = false) => ({ name, type, label, primaryKey, masked: false, }); /** 主数据一律 `kind: 'master'`:按业务实体分行,没有单标识 */ const master = (domain: string, object: string, label: string, fields: ReturnType[]) => objectDeclarationSchema.parse({ domain, object, label, kind: 'master', fields }); export const MASTER_OBJECTS: readonly ObjectDeclaration[] = [ /* ── 客户:谁在买 ──────────────────────────────────────────────────── */ master('customer', 'profile', '客户档案', [ f('customer_code', 'text', '客户编码', true), f('name', 'text', '客户名称'), f('grade', 'text', '客户等级'), f('industry', 'text', '所属行业'), f('region', 'text', '区域'), f('owner', 'text', '负责 BD'), f('payment_terms', 'text', '付款条件'), f('credit_limit', 'number', '信用额度'), /* 画像标签:价格敏感度、决策周期、常用条款 —— A7 归档时回写的就是这一列 */ f('profile_tags', 'text', '画像标签'), f('status', 'text', '状态'), ]), master('customer', 'contact', '客户联系人', [ f('customer_code', 'text', '客户编码', true), f('contact_code', 'text', '联系人编码', true), f('name', 'text', '姓名'), f('title', 'text', '职务'), /* 邮箱与电话是个人信息 —— 主数据里必须标脱敏,治理面照此裁剪 */ { ...f('email', 'text', '邮箱'), masked: true }, { ...f('phone', 'text', '电话'), masked: true }, ]), master('customer', 'price_terms', '客户价格条款', [ f('customer_code', 'text', '客户编码', true), f('category', 'text', '品类', true), f('target_margin', 'number', '目标毛利'), f('discount', 'number', '折扣'), f('valid_until', 'date', '有效期至'), ]), /* ── 项目:为什么买 —— 客户的产品项目,反复询价的载体 ───────────────── */ master('project', 'profile', '项目档案', [ f('project_code', 'text', '项目编码', true), f('customer_code', 'text', '所属客户'), f('name', 'text', '项目名称'), /* BOM 早期 / BOM 后期 / 量产 —— A3 的询价打法就按这个分 */ f('stage', 'text', '项目阶段'), f('annual_volume', 'integer', '年用量预估'), f('start_at', 'date', '立项日期'), f('owner', 'text', '负责人'), f('status', 'text', '状态'), ]), master('project', 'bom', '项目 BOM', [ f('project_code', 'text', '项目编码', true), f('pn', 'text', '料号', true), f('qty_per_unit', 'number', '单机用量'), f('eau', 'integer', '年需求量'), f('designed_in', 'boolean', '已导入设计'), f('status', 'text', '状态'), ]), /* ── 供应商:从哪买 ───────────────────────────────────────────────── */ master('supplier', 'profile', '供应商档案', [ f('supplier_code', 'text', '供应商编码', true), f('name', 'text', '供应商名称'), /* 原厂 / 授权代理 / 贸易商 —— 「现货场景不得选无库存转单商」判据读的就是它 */ f('type', 'text', '供应商类型'), f('region', 'text', '区域'), f('rating', 'text', '评级'), f('payment_terms', 'text', '账期'), f('status', 'text', '状态'), ]), master('supplier', 'line_card', '代理线卡', [ f('supplier_code', 'text', '供应商编码', true), f('brand', 'text', '品牌', true), f('authorized', 'boolean', '是否授权'), f('lead_time_typ', 'text', '常规交期'), ]), master('supplier', 'contact', '供应商联系人', [ f('supplier_code', 'text', '供应商编码', true), f('contact_code', 'text', '联系人编码', true), f('name', 'text', '姓名'), { ...f('email', 'text', '邮箱'), masked: true }, { ...f('phone', 'text', '电话'), masked: true }, ]), /* ── 市场:买什么、行情如何 ────────────────────────────────────────── 料号主数据放这一域:ERP 里它常自成一域(物料主数据), 但这门生意关心的是它**在市场上什么状态**(生命周期、有没有货、什么价), 跟行情摆在一起更顺手。要独立成域也说得通,记在这儿备考。 */ master('market', 'part', '料号主数据', [ f('pn', 'text', '料号', true), f('brand', 'text', '品牌'), f('category', 'text', '品类'), f('package', 'text', '封装'), /* Active / NRND / EOL —— A1 三分类与 A2 选型都读它 */ f('lifecycle', 'text', '生命周期'), f('rohs', 'boolean', 'RoHS'), f('datasheet_url', 'text', '规格书链接'), ]), master('market', 'price_history', '历史价格情报', [ f('pn', 'text', '料号', true), f('quoted_at', 'date', '报价日期', true), f('customer_code', 'text', '客户编码'), f('unit_price', 'number', '成交/报出单价'), f('lead_time', 'text', '交期'), f('lost_reason', 'text', '未成交原因'), f('competitor_price', 'number', '竞对价位'), ]), master('market', 'quote_index', '市场行情', [ f('pn', 'text', '料号', true), f('observed_at', 'date', '观测日期', true), f('source', 'text', '来源'), f('price', 'number', '市场价'), 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', 'parts', 'demand', 'substitution', 'validation'] as const; /** * **样例客户** —— 让「老客户」那条路试得出来。 * * 主数据四域建出来是空的,而 A1 的第一个状态有两条路: * 认得出客户就直接过、认不出才请人建档。库里一个客户都没有的话, * 两条样例跑起来一模一样 —— **第二条路根本试不到**。 * * 所以随主数据一起播一位客户:样例 2 的来信就用他的邮箱发。 * 只播这一位,且是**幂等的按主键写**:它是让样例跑得起来的最小夹具, * 不是往主数据里塞演示数据 —— 真实客户由 A1 建档时一位一位落进来。 */ export const MASTER_SAMPLE_ROWS: readonly { address: LedgerAddress; key: LedgerKey; values: LedgerRow }[] = [ /* * **Itelma** —— 老客户测试案例的那一位(照 `JCAI_PRD/docs/活动执行页/` 两份目标件)。 * 俄罗斯车规电子、项目制;客户码 CU-0142 就是需求件上写的「已命中客户资料库」那一条。 */ { address: { domain: 'customer', object: 'profile' }, key: { customer_code: 'CU-0142' }, values: { customer_code: 'CU-0142', name: 'Itelma', grade: 'A', industry: '车规电子', region: '俄罗斯', payment_terms: 'EXW · CNY 含税', profile_tags: '项目制 · 按项目批量询价 · 认证要求严(AEC-Q200)', status: 'active', /* * E04(关系覆盖与关键人经营)字段 —— 2026-08-20 补齐,让「客户已经跑过 E04、 * 找缺口」这条分支第一次能真被样例跑通(此前这几列全空,每轮都只能从零起草)。 * decision_chain 只覆盖了 bt-01(采购层),bt-02/bt-03 是刻意留的缺口。 */ cust_type: 'Tier1 OEM', coverage_score: 35, decision_chain: { nodes: [ { node: 'bt-01', btLevel: 'bt-01', required: true, contactCode: 'CU-0142-01', covered: true }, { node: 'bt-02', btLevel: 'bt-02', required: true, contactCode: '', covered: false }, { node: 'bt-03', btLevel: 'bt-03', required: true, contactCode: '', covered: false }, ], }, interaction_timeline: { entries: [ { date: '2026-06-15', type: '邮件报价跟进', contactCode: 'CU-0142-01', note: 'MD14.1 项目 BOM 报价确认' }, { date: '2026-07-20', type: '电话沟通', contactCode: 'CU-0142-01', note: '交期与付款方式确认' }, ], }, }, }, { address: { domain: 'customer', object: 'contact' }, key: { customer_code: 'CU-0142', contact_code: 'CU-0142-01' }, values: { customer_code: 'CU-0142', contact_code: 'CU-0142-01', name: 'Ekaterina Volkova', title: '采购部', // **认人的钥匙**:老客户样例的来信就从这个邮箱发,A1 第一步照它认得出 email: 'e.volkova@itelma.ru', // E04 字段 —— 对应上面 decision_chain 的 bt-01 节点 role: '采购', decision_node: 'bt-01', influence: 'high', relationship: 3, comm_pref: 'Email', key_person: true, }, }, /* * 项目档案 —— 这一单询的是 MD14.1 这个项目的料。 * 播它是因为需求件上「项目制」那句话得有出处:客户属性说得出「按项目询价」, * 库里就该有那个项目,否则那句话是装出来的。 */ { address: { domain: 'project', object: 'profile' }, key: { project_code: 'MD14.1' }, values: { project_code: 'MD14.1', customer_code: 'CU-0142', name: 'MD14.1 车身控制器', stage: 'BOM 后期', status: 'active', }, }, /* * **CU-0087** —— E04(关系覆盖与关键人经营)"关键人离职/调岗"样例专用夹具 * (2026-08-20 补):此前这条样例引用的 CU-0087 在种子数据里根本不存在, * 是一处遗留 bug。这里种一位客户 + 两位联系人,decision_chain 的 bt-01 * (采购层)当下由陈刚覆盖 —— 样例说的正是"这个人要走了,这个节点要重新覆盖"。 */ { address: { domain: 'customer', object: 'profile' }, key: { customer_code: 'CU-0087' }, values: { customer_code: 'CU-0087', name: '深圳精密电子有限公司', grade: 'B', industry: '工业自动化', region: '中国·深圳', payment_terms: 'T/T 30 天', profile_tags: '批量稳定 · 价格敏感', status: 'active', cust_type: '模组厂', coverage_score: 55, decision_chain: { nodes: [ { node: 'bt-01', btLevel: 'bt-01', required: true, contactCode: 'CU-0087-01', covered: true }, { node: 'bt-02', btLevel: 'bt-02', required: true, contactCode: 'CU-0087-02', covered: true }, { node: 'bt-03', btLevel: 'bt-03', required: false, contactCode: '', covered: false }, ], }, interaction_timeline: { entries: [{ date: '2026-05-10', type: '现场拜访', contactCode: 'CU-0087-01', note: '年度框架协议签署' }], }, }, }, { address: { domain: 'customer', object: 'contact' }, key: { customer_code: 'CU-0087', contact_code: 'CU-0087-01' }, values: { customer_code: 'CU-0087', contact_code: 'CU-0087-01', name: 'Chen Gang', title: '采购部长', // **样例说的就是这个人**:「关键人离职/调岗」引用的正是这一位 email: 'chen.gang@sz-precision.com', role: '采购', decision_node: 'bt-01', influence: 'high', relationship: 4, comm_pref: 'Email', key_person: true, }, }, { address: { domain: 'customer', object: 'contact' }, key: { customer_code: 'CU-0087', contact_code: 'CU-0087-02' }, values: { customer_code: 'CU-0087', contact_code: 'CU-0087-02', name: 'Li Wei', title: '技术总监', email: 'li.wei@sz-precision.com', role: '技术决策人', decision_node: 'bt-02', influence: 'medium', relationship: 3, comm_pref: 'Email', key_person: false, }, }, /* * I01(品牌形象与销售工具包管理)读的 L-01 宏观市场台账 —— 两条,呼应 I01 的两条 * 事件驱动样例(「进入俄罗斯市场」「E04 请求定向素材」),让「读市场信号 → 拟内容」 * 这条分支第一次有真数据可读,不必每轮都读空手。 */ { address: { domain: 'market', object: 'signal' }, key: { signal_id: 'MS-2026-0001' }, values: { signal_id: 'MS-2026-0001', signal_type: 'industry_trend', title: '俄罗斯车规电子本土化替代加速', content: '受供应链管制影响,俄罗斯本土车企与 Tier1 加快引入非欧美芯片供应商,车规级 AEC-Q200 认证成为准入门槛,采购决策周期缩短、更看重供货稳定性承诺。', source: '行业报告', signal_date: '2026-06-01', market_region: '俄罗斯', industry: '汽车电子', chip_category: 'MCU', importance: 4, impact_dir: 'opportunity', signal_status: 'evaluated', version: '2026-Q2', review_note: '', }, }, { address: { domain: 'market', object: 'signal' }, key: { signal_id: 'MS-2026-0002' }, values: { signal_id: 'MS-2026-0002', signal_type: 'customer_market', title: 'Itelma 系客户新一轮车身控制器项目招标', content: 'Itelma 及同类俄罗斯 Tier1 客户密集启动车身控制器新项目招标,价值传递素材需突出认证齐全度与批量交付能力,系统方案白皮书是决策链高层最看重的材料形式。', source: '订阅', signal_date: '2026-07-10', market_region: '俄罗斯', industry: '汽车电子', chip_category: 'MCU', importance: 5, impact_dir: 'opportunity', signal_status: 'evaluated', version: '2026-Q2', 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;