@hunter-mate/sync-entities 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HiredChina
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ import type { SyncEntityDef } from './types';
2
+ export declare const SYNC_ENTITIES: SyncEntityDef[];
3
+ /** key → 定义 索引 */
4
+ export declare const SYNC_ENTITY_MAP: Readonly<Record<string, SyncEntityDef>>;
5
+ export declare function getSyncEntity(key: string): SyncEntityDef | undefined;
6
+ export declare function isSyncEntityKey(key: string): boolean;
@@ -0,0 +1,662 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SYNC_ENTITY_MAP = exports.SYNC_ENTITIES = void 0;
4
+ exports.getSyncEntity = getSyncEntity;
5
+ exports.isSyncEntityKey = isSyncEntityKey;
6
+ /**
7
+ * 同步实体注册元数据 —— 唯一 SSOT(FID-098-hm-data-sync iter3)
8
+ *
9
+ * 与 hunter-mate 实体类一一对应,字段名 = 实体属性名。
10
+ * 注意:这是同步契约的数据面定义,hunter-mate 实体增删字段时此处同步更新
11
+ * (dev-ops vendor 副本经 bin/sync-hm-sync-entities.sh 刷新,hc-hw 有漂移回归)。
12
+ */
13
+ const ts = (name, label) => ({
14
+ name,
15
+ label,
16
+ kind: 'text',
17
+ });
18
+ const num = (name, label) => ({
19
+ name,
20
+ label,
21
+ kind: 'number',
22
+ });
23
+ const bool = (name, label) => ({
24
+ name,
25
+ label,
26
+ kind: 'boolean',
27
+ });
28
+ const dt = (name, label) => ({
29
+ name,
30
+ label,
31
+ kind: 'datetime',
32
+ });
33
+ const json = (name, label) => ({
34
+ name,
35
+ label,
36
+ kind: 'json',
37
+ });
38
+ const JOB_CASE_STATUS = {
39
+ draft: '草稿',
40
+ active: '进行中',
41
+ archived: '已归档',
42
+ };
43
+ exports.SYNC_ENTITIES = [
44
+ {
45
+ key: 'job_cases',
46
+ className: 'JobCase',
47
+ label: '职位 Case',
48
+ direction: 'both',
49
+ description: '猎头负责的职位需求(case),推荐记忆闭环锚点',
50
+ list: { titleField: 'title', subtitleField: 'status', timeField: 'updatedAt' },
51
+ detail: {
52
+ sections: [
53
+ { title: '基本信息', fields: ['title', 'externalId', 'status', 'customerId', 'ownerId', 'folderPath'] },
54
+ { title: '需求明细', fields: ['requirements'] },
55
+ { title: '原始文件', fields: ['rawFiles'] },
56
+ { title: '系统', fields: ['id', 'createdAt', 'updatedAt'] },
57
+ ],
58
+ },
59
+ fields: [
60
+ ts('title', '职位标题'),
61
+ ts('externalId', '外部 ID'),
62
+ {
63
+ name: 'status',
64
+ label: '状态',
65
+ kind: 'enum',
66
+ enumLabels: JOB_CASE_STATUS,
67
+ },
68
+ num('customerId', '关联客户 ID'),
69
+ num('ownerId', '归属人 ID'),
70
+ ts('folderPath', '文件夹路径'),
71
+ json('requirements', '需求明细'),
72
+ json('rawFiles', '原始文件'),
73
+ num('id', 'ID'),
74
+ dt('createdAt', '创建时间'),
75
+ dt('updatedAt', '更新时间'),
76
+ ],
77
+ },
78
+ {
79
+ key: 'communication_records',
80
+ className: 'CommunicationRecord',
81
+ label: '沟通记录',
82
+ direction: 'both',
83
+ description: '与候选人/客户的沟通(电话/微信/邮件/面谈)及 AI 纪要',
84
+ list: { titleField: 'type', subtitleField: 'status', timeField: 'updatedAt' },
85
+ detail: {
86
+ sections: [
87
+ { title: '基本信息', fields: ['type', 'status', 'externalId', 'personId', 'jobCaseId', 'ownerId'] },
88
+ { title: '内容', fields: ['transcript'] },
89
+ { title: 'AI 提取', fields: ['summary', 'extractedEntities'] },
90
+ { title: '系统', fields: ['id', 'createdAt', 'updatedAt'] },
91
+ ],
92
+ },
93
+ fields: [
94
+ {
95
+ name: 'type',
96
+ label: '沟通方式',
97
+ kind: 'enum',
98
+ enumLabels: { call: '电话', wechat: '微信', email: '邮件', audio: '语音', meeting: '面谈' },
99
+ },
100
+ ts('status', '状态'),
101
+ ts('externalId', '外部 ID'),
102
+ num('personId', '人才 ID'),
103
+ num('jobCaseId', '职位 Case ID'),
104
+ num('ownerId', '归属人 ID'),
105
+ { ...ts('transcript', '沟通原文'), long: true },
106
+ json('summary', 'AI 纪要'),
107
+ json('extractedEntities', 'AI 提取实体'),
108
+ num('id', 'ID'),
109
+ dt('createdAt', '创建时间'),
110
+ dt('updatedAt', '更新时间'),
111
+ ],
112
+ },
113
+ {
114
+ key: 'remarks',
115
+ className: 'Remark',
116
+ label: '备注',
117
+ direction: 'both',
118
+ description: '挂在人才/职位/公司/客户/匹配上的业务备注',
119
+ list: { titleField: 'content', subtitleField: 'category', timeField: 'createdAt' },
120
+ detail: {
121
+ sections: [
122
+ { title: '基本信息', fields: ['targetType', 'targetId', 'category', 'author'] },
123
+ { title: '内容', fields: ['content'] },
124
+ { title: '附件', fields: ['tags', 'attachments'] },
125
+ { title: '系统', fields: ['id', 'createdAt'] },
126
+ ],
127
+ },
128
+ fields: [
129
+ ts('targetType', '目标类型'),
130
+ ts('targetId', '目标 ID'),
131
+ ts('category', '分类'),
132
+ json('author', '作者'),
133
+ { ...ts('content', '内容'), long: true },
134
+ json('tags', '标签'),
135
+ json('attachments', '附件'),
136
+ ts('id', 'ID'),
137
+ dt('createdAt', '创建时间'),
138
+ ],
139
+ },
140
+ {
141
+ key: 'parsed_resumes',
142
+ className: 'ParsedResume',
143
+ label: '解析简历',
144
+ direction: 'both',
145
+ description: 'HBR 简历解析产物:标准化履历/技能/教育/期望职位',
146
+ list: { titleField: 'snapshotId', subtitleField: 'talentPortraitId', timeField: 'updatedAt' },
147
+ detail: {
148
+ sections: [
149
+ { title: '索引', fields: ['snapshotId', 'talentPortraitId'] },
150
+ { title: '解析任务', fields: ['parseJob'] },
151
+ { title: '来源', fields: ['source'] },
152
+ { title: '提取结果', fields: ['extracted', 'confidence', 'rawExtracted'] },
153
+ { title: '系统', fields: ['id', 'createdAt', 'updatedAt'] },
154
+ ],
155
+ },
156
+ fields: [
157
+ ts('snapshotId', '原始快照 ID'),
158
+ ts('talentPortraitId', '人才画像 ID'),
159
+ json('parseJob', '解析任务'),
160
+ json('source', '来源'),
161
+ json('extracted', '提取结果'),
162
+ json('confidence', '置信度'),
163
+ json('rawExtracted', '原始提取'),
164
+ ts('id', 'ID'),
165
+ dt('createdAt', '创建时间'),
166
+ dt('updatedAt', '更新时间'),
167
+ ],
168
+ },
169
+ {
170
+ key: 'talent_portraits',
171
+ className: 'TalentPortrait',
172
+ label: '人才画像',
173
+ direction: 'both',
174
+ description: '跨来源归一的人才主档案:身份/联系方式/职业/归属',
175
+ list: { titleField: 'identity.displayName', subtitleField: 'professional.currentTitle', timeField: 'updatedAt' },
176
+ detail: {
177
+ sections: [
178
+ { title: '身份', fields: ['identity'] },
179
+ { title: '联系方式', fields: ['contact'] },
180
+ { title: '职业信息', fields: ['professional'] },
181
+ { title: '归属', fields: ['ownership'] },
182
+ { title: '溯源', fields: ['portraitProvenance', 'stats'] },
183
+ { title: '系统', fields: ['masterRecordId', 'id', 'createdAt', 'updatedAt'] },
184
+ ],
185
+ },
186
+ fields: [
187
+ json('identity', '身份'),
188
+ json('contact', '联系方式'),
189
+ json('professional', '职业信息'),
190
+ json('ownership', '归属'),
191
+ json('portraitProvenance', '字段溯源'),
192
+ json('stats', '统计'),
193
+ ts('masterRecordId', '主记录 ID'),
194
+ ts('id', 'ID'),
195
+ dt('createdAt', '创建时间'),
196
+ dt('updatedAt', '更新时间'),
197
+ ],
198
+ },
199
+ {
200
+ key: 'talent_fingerprints',
201
+ className: 'TalentFingerprint',
202
+ label: '人才指纹',
203
+ direction: 'both',
204
+ description: '去重匹配指纹:强标识(电话/邮箱 hash)+ 弱标识组合',
205
+ list: { titleField: 'portraitId', timeField: 'updatedAt' },
206
+ detail: {
207
+ sections: [
208
+ { title: '强标识', fields: ['strong'] },
209
+ { title: '弱标识', fields: ['weak'] },
210
+ { title: '人脸', fields: ['face'] },
211
+ { title: '匹配历史', fields: ['matchHistory'] },
212
+ { title: '系统', fields: ['portraitId', 'createdAt', 'updatedAt'] },
213
+ ],
214
+ },
215
+ fields: [
216
+ ts('portraitId', '人才画像 ID'),
217
+ json('strong', '强标识'),
218
+ json('weak', '弱标识'),
219
+ json('face', '人脸特征'),
220
+ json('matchHistory', '匹配历史'),
221
+ dt('createdAt', '创建时间'),
222
+ dt('updatedAt', '更新时间'),
223
+ ],
224
+ },
225
+ {
226
+ key: 'talent_job_matches',
227
+ className: 'TalentJobMatch',
228
+ label: '人才职位匹配',
229
+ direction: 'both',
230
+ description: '人才与职位 case 的匹配评分 + AI 推荐 + 人工反馈',
231
+ list: { titleField: 'portraitId', subtitleField: 'status', timeField: 'updatedAt' },
232
+ detail: {
233
+ sections: [
234
+ { title: '基本信息', fields: ['portraitId', 'jobCaseId', 'status'] },
235
+ { title: '评分', fields: ['scores', 'matchDetails'] },
236
+ { title: 'AI 推荐', fields: ['recommendation'] },
237
+ { title: '人工反馈', fields: ['humanFeedback'] },
238
+ { title: '系统', fields: ['id', 'createdAt', 'updatedAt'] },
239
+ ],
240
+ },
241
+ fields: [
242
+ ts('portraitId', '人才画像 ID'),
243
+ num('jobCaseId', '职位 Case ID'),
244
+ ts('status', '状态'),
245
+ json('scores', '评分'),
246
+ json('matchDetails', '匹配明细'),
247
+ json('recommendation', 'AI 推荐'),
248
+ json('humanFeedback', '人工反馈'),
249
+ ts('id', 'ID'),
250
+ dt('createdAt', '创建时间'),
251
+ dt('updatedAt', '更新时间'),
252
+ ],
253
+ },
254
+ {
255
+ key: 'parse_error_reports',
256
+ className: 'ParseErrorReport',
257
+ label: '解析错误报告',
258
+ direction: 'both',
259
+ description: 'HBR 解析失败上报,含 Sentry 联动状态',
260
+ list: { titleField: 'parserId', subtitleField: 'errorMessage', timeField: 'createdAt' },
261
+ detail: {
262
+ sections: [
263
+ { title: '基本信息', fields: ['parserId', 'snapshotId', 'reportedToSentry', 'sentryEventId'] },
264
+ { title: '错误', fields: ['errorMessage', 'errorStack'] },
265
+ { title: '上下文', fields: ['context'] },
266
+ { title: '系统', fields: ['id', 'createdAt'] },
267
+ ],
268
+ },
269
+ fields: [
270
+ ts('parserId', '解析器 ID'),
271
+ ts('snapshotId', '快照 ID'),
272
+ bool('reportedToSentry', '已报 Sentry'),
273
+ ts('sentryEventId', 'Sentry 事件 ID'),
274
+ { ...ts('errorMessage', '错误信息'), long: true },
275
+ { ...ts('errorStack', '错误堆栈'), long: true },
276
+ json('context', '上下文'),
277
+ ts('id', 'ID'),
278
+ dt('createdAt', '创建时间'),
279
+ ],
280
+ },
281
+ {
282
+ key: 'site_registry',
283
+ className: 'SiteRegistry',
284
+ label: '站点注册表',
285
+ direction: 'both',
286
+ description: '招聘域清单:扩展注入/adapter 映射/启发式发现',
287
+ list: { titleField: 'domain', subtitleField: 'platform', timeField: 'lastSeenAt' },
288
+ detail: {
289
+ sections: [
290
+ {
291
+ title: '基本信息',
292
+ fields: ['domain', 'platform', 'adapterPlatform', 'source', 'enabled'],
293
+ },
294
+ { title: '匹配配置', fields: ['matcherConfig'] },
295
+ { title: '发现证据', fields: ['evidence', 'sampleUrl'] },
296
+ { title: '系统', fields: ['id', 'firstSeenAt', 'lastSeenAt'] },
297
+ ],
298
+ },
299
+ fields: [
300
+ ts('domain', '域名'),
301
+ ts('platform', '平台'),
302
+ ts('adapterPlatform', 'Adapter 平台'),
303
+ {
304
+ name: 'source',
305
+ label: '来源',
306
+ kind: 'enum',
307
+ enumLabels: {
308
+ builtin: '内置',
309
+ user: '用户',
310
+ auto: '自动发现',
311
+ ignored: '已忽略',
312
+ harness: 'Harness',
313
+ },
314
+ },
315
+ bool('enabled', '已启用'),
316
+ json('matcherConfig', '匹配配置'),
317
+ { ...ts('evidence', '发现证据'), long: true },
318
+ ts('sampleUrl', '样本 URL'),
319
+ num('id', 'ID'),
320
+ dt('firstSeenAt', '首次发现'),
321
+ dt('lastSeenAt', '最近发现'),
322
+ ],
323
+ },
324
+ {
325
+ key: 'talent_profiles',
326
+ className: 'TalentProfile',
327
+ label: '人才档案',
328
+ direction: 'both',
329
+ description: '网页解析入库的人才实体(归一化展示字段 + 原始提取)',
330
+ list: { titleField: 'name', subtitleField: 'headline', timeField: 'updatedAt' },
331
+ detail: {
332
+ sections: [
333
+ { title: '基本信息', fields: ['name', 'headline', 'source', 'ruleVersion'] },
334
+ { title: '来源', fields: ['sourceUrl', 'sessionId', 'tags'] },
335
+ { title: '提取', fields: ['extracted', 'raw'] },
336
+ { title: '系统', fields: ['id', 'dedupKey', 'createdAt', 'updatedAt'] },
337
+ ],
338
+ },
339
+ fields: [
340
+ ts('name', '姓名'),
341
+ ts('headline', 'Headline/头衔'),
342
+ ts('source', '来源系统'),
343
+ ts('ruleVersion', '规则版本'),
344
+ ts('sourceUrl', '来源 URL'),
345
+ ts('sessionId', '采集会话'),
346
+ json('tags', '标签'),
347
+ json('extracted', '规范化字段'),
348
+ json('raw', '原始提取'),
349
+ num('id', 'ID'),
350
+ ts('dedupKey', '去重键'),
351
+ dt('createdAt', '创建时间'),
352
+ dt('updatedAt', '更新时间'),
353
+ ],
354
+ },
355
+ {
356
+ key: 'job_postings',
357
+ className: 'JobPosting',
358
+ label: '职位发布',
359
+ direction: 'both',
360
+ description: '网页解析入库的职位 JD 实体',
361
+ list: { titleField: 'title', subtitleField: 'company', timeField: 'updatedAt' },
362
+ detail: {
363
+ sections: [
364
+ { title: '基本信息', fields: ['title', 'company', 'source', 'ruleVersion'] },
365
+ { title: '来源', fields: ['sourceUrl', 'sessionId', 'tags'] },
366
+ { title: '提取', fields: ['extracted', 'raw'] },
367
+ { title: '系统', fields: ['id', 'dedupKey', 'createdAt', 'updatedAt'] },
368
+ ],
369
+ },
370
+ fields: [
371
+ ts('title', '职位标题'),
372
+ ts('company', '公司'),
373
+ ts('source', '来源系统'),
374
+ ts('ruleVersion', '规则版本'),
375
+ ts('sourceUrl', '来源 URL'),
376
+ ts('sessionId', '采集会话'),
377
+ json('tags', '标签'),
378
+ json('extracted', '规范化字段'),
379
+ json('raw', '原始提取'),
380
+ num('id', 'ID'),
381
+ ts('dedupKey', '去重键'),
382
+ dt('createdAt', '创建时间'),
383
+ dt('updatedAt', '更新时间'),
384
+ ],
385
+ },
386
+ {
387
+ key: 'company_profiles',
388
+ className: 'CompanyProfile',
389
+ label: '公司档案',
390
+ direction: 'both',
391
+ description: '网页解析入库的公司实体',
392
+ list: { titleField: 'name', subtitleField: 'source', timeField: 'updatedAt' },
393
+ detail: {
394
+ sections: [
395
+ { title: '基本信息', fields: ['name', 'source', 'ruleVersion'] },
396
+ { title: '来源', fields: ['sourceUrl', 'sessionId', 'tags'] },
397
+ { title: '提取', fields: ['extracted', 'raw'] },
398
+ { title: '系统', fields: ['id', 'dedupKey', 'createdAt', 'updatedAt'] },
399
+ ],
400
+ },
401
+ fields: [
402
+ ts('name', '公司名'),
403
+ ts('source', '来源系统'),
404
+ ts('ruleVersion', '规则版本'),
405
+ ts('sourceUrl', '来源 URL'),
406
+ ts('sessionId', '采集会话'),
407
+ json('tags', '标签'),
408
+ json('extracted', '规范化字段'),
409
+ json('raw', '原始提取'),
410
+ num('id', 'ID'),
411
+ ts('dedupKey', '去重键'),
412
+ dt('createdAt', '创建时间'),
413
+ dt('updatedAt', '更新时间'),
414
+ ],
415
+ },
416
+ {
417
+ key: 'adapter_rules',
418
+ className: 'AdapterRule',
419
+ label: 'Adapter 规则',
420
+ direction: 'both',
421
+ description: '按平台+版本+URL 匹配的快照提取规则(可 dev-ops 动态下发)',
422
+ list: { titleField: 'platform', subtitleField: 'version', timeField: 'createdAt' },
423
+ detail: {
424
+ sections: [
425
+ {
426
+ title: '基本信息',
427
+ fields: ['platform', 'version', 'ruleType', 'priority', 'enabled', 'source', 'externalId'],
428
+ },
429
+ { title: '匹配', fields: ['hostPattern', 'pathPattern'] },
430
+ { title: '规则内容', fields: ['rulePayload'] },
431
+ { title: '系统', fields: ['id', 'createdAt'] },
432
+ ],
433
+ },
434
+ fields: [
435
+ ts('platform', '平台'),
436
+ ts('version', '版本'),
437
+ {
438
+ name: 'ruleType',
439
+ label: '规则类型',
440
+ kind: 'enum',
441
+ enumLabels: { selector: '选择器', regex: '正则', script: '脚本', jsonld: 'JSON-LD' },
442
+ },
443
+ num('priority', '优先级'),
444
+ bool('enabled', '已启用'),
445
+ {
446
+ name: 'source',
447
+ label: '来源',
448
+ kind: 'enum',
449
+ enumLabels: { builtin: '内置', harness: 'Harness overlay' },
450
+ },
451
+ ts('externalId', '外部文件标识'),
452
+ ts('hostPattern', '主机匹配'),
453
+ ts('pathPattern', '路径匹配'),
454
+ json('rulePayload', '规则内容'),
455
+ num('id', 'ID'),
456
+ dt('createdAt', '创建时间'),
457
+ ],
458
+ },
459
+ {
460
+ key: 'extraction_rules',
461
+ className: 'ExtractionRule',
462
+ label: '提取规则',
463
+ direction: 'both',
464
+ description: '字段级提取规则(决定产出哪类实体)',
465
+ list: { titleField: 'ruleId', subtitleField: 'targetHost', timeField: 'createdAt' },
466
+ detail: {
467
+ sections: [
468
+ {
469
+ title: '基本信息',
470
+ fields: ['ruleId', 'version', 'entityType', 'ruleType', 'priority', 'enabled'],
471
+ },
472
+ { title: '匹配', fields: ['targetHost', 'targetPathPattern'] },
473
+ { title: '规则内容', fields: ['rulePayload', 'extractedSchema'] },
474
+ { title: '系统', fields: ['id', 'createdAt'] },
475
+ ],
476
+ },
477
+ fields: [
478
+ ts('ruleId', '规则 ID'),
479
+ ts('version', '版本'),
480
+ ts('entityType', '产出实体类型'),
481
+ {
482
+ name: 'ruleType',
483
+ label: '规则类型',
484
+ kind: 'enum',
485
+ enumLabels: { selector: '选择器', regex: '正则', script: '脚本' },
486
+ },
487
+ num('priority', '优先级'),
488
+ bool('enabled', '已启用'),
489
+ ts('targetHost', '目标主机'),
490
+ ts('targetPathPattern', '目标路径'),
491
+ json('rulePayload', '规则内容'),
492
+ json('extractedSchema', '产出字段 schema'),
493
+ num('id', 'ID'),
494
+ dt('createdAt', '创建时间'),
495
+ ],
496
+ },
497
+ {
498
+ key: 'customers',
499
+ className: 'Customer',
500
+ label: '客户',
501
+ direction: 'both',
502
+ description: '用人单位归一化档案(推荐记忆闭环锚点)',
503
+ list: { titleField: 'canonicalName', subtitleField: 'typeStatus', timeField: 'updatedAt' },
504
+ detail: {
505
+ sections: [
506
+ { title: '基本信息', fields: ['canonicalName', 'aliases', 'typeStatus', 'ownerId'] },
507
+ { title: '联系与交易', fields: ['contacts', 'purchaseHistory'] },
508
+ { title: '备注', fields: ['notes', 'activityLogIds'] },
509
+ { title: '系统', fields: ['id', 'dedupKey', 'createdAt', 'updatedAt'] },
510
+ ],
511
+ },
512
+ fields: [
513
+ ts('canonicalName', '规范名称'),
514
+ json('aliases', '别名'),
515
+ json('typeStatus', '类型/状态'),
516
+ num('ownerId', '归属人 ID'),
517
+ json('contacts', '联系人'),
518
+ json('purchaseHistory', '交易历史'),
519
+ { ...ts('notes', '备注'), long: true },
520
+ json('activityLogIds', '动态日志 ID'),
521
+ num('id', 'ID'),
522
+ ts('dedupKey', '去重键'),
523
+ dt('createdAt', '创建时间'),
524
+ dt('updatedAt', '更新时间'),
525
+ ],
526
+ },
527
+ {
528
+ key: 'preferences',
529
+ className: 'Preference',
530
+ label: '偏好',
531
+ direction: 'both',
532
+ description: '三层偏好(user/customer/case):推荐与反馈沉淀',
533
+ list: { titleField: 'dimension', subtitleField: 'scope', timeField: 'updatedAt' },
534
+ detail: {
535
+ sections: [
536
+ {
537
+ title: '基本信息',
538
+ fields: ['scope', 'scopeId', 'userId', 'dimension', 'weight'],
539
+ },
540
+ { title: '偏好值', fields: ['value', 'valueKey'] },
541
+ { title: '证据', fields: ['evidence'] },
542
+ { title: '系统', fields: ['id', 'createdAt', 'updatedAt'] },
543
+ ],
544
+ },
545
+ fields: [
546
+ {
547
+ name: 'scope',
548
+ label: '作用域',
549
+ kind: 'enum',
550
+ enumLabels: { user: '顾问', customer: '客户', case: '职位 Case' },
551
+ },
552
+ num('scopeId', '作用域 ID'),
553
+ num('userId', '归属人 ID'),
554
+ ts('dimension', '维度'),
555
+ json('value', '偏好值'),
556
+ ts('valueKey', '偏好值键'),
557
+ num('weight', '权重'),
558
+ { ...ts('evidence', '证据来源'), long: true },
559
+ num('id', 'ID'),
560
+ dt('createdAt', '创建时间'),
561
+ dt('updatedAt', '更新时间'),
562
+ ],
563
+ },
564
+ {
565
+ key: 'recording_snapshots',
566
+ className: 'RecordingSnapshot',
567
+ label: '录制快照',
568
+ direction: 'push_only',
569
+ description: 'rrweb full snapshot 重建 HTML(只备份,不回灌)',
570
+ list: { titleField: 'domain', subtitleField: 'pageClass', timeField: 'snapshotTimestamp' },
571
+ detail: {
572
+ sections: [
573
+ {
574
+ title: '基本信息',
575
+ fields: ['sessionId', 'eventId', 'domain', 'pageClass', 'sourceUrl', 'isCheckout'],
576
+ },
577
+ { title: '解析状态', fields: ['adapterVersion', 'parseStatus', 'anomalyScore', 'reportedAt'] },
578
+ { title: '内容', fields: ['html'] },
579
+ { title: '系统', fields: ['id', 'snapshotTimestamp', 'createdAt'] },
580
+ ],
581
+ },
582
+ fields: [
583
+ ts('sessionId', '录制会话'),
584
+ num('eventId', '事件 ID'),
585
+ ts('domain', '域名'),
586
+ ts('pageClass', '页面分类'),
587
+ ts('sourceUrl', '来源 URL'),
588
+ bool('isCheckout', 'Checkout 触发'),
589
+ ts('adapterVersion', 'Adapter 版本'),
590
+ ts('parseStatus', '解析状态'),
591
+ num('anomalyScore', '异常评分'),
592
+ dt('reportedAt', '上报时间'),
593
+ { ...ts('html', 'HTML'), long: true },
594
+ num('id', 'ID'),
595
+ dt('snapshotTimestamp', '快照时间'),
596
+ dt('createdAt', '创建时间'),
597
+ ],
598
+ },
599
+ {
600
+ key: 'recording_events',
601
+ className: 'RecordingEvent',
602
+ label: '录制事件',
603
+ direction: 'push_only',
604
+ description: 'rrweb 原始事件流(只备份,不回灌)',
605
+ list: { titleField: 'eventType', subtitleField: 'sourceUrl', timeField: 'createdAt' },
606
+ detail: {
607
+ sections: [
608
+ { title: '基本信息', fields: ['sessionId', 'eventType', 'sourceUrl', 'tabId'] },
609
+ { title: '内容', fields: ['payload'] },
610
+ { title: '系统', fields: ['id', 'createdAt'] },
611
+ ],
612
+ },
613
+ fields: [
614
+ ts('sessionId', '录制会话'),
615
+ ts('eventType', '事件类型'),
616
+ ts('sourceUrl', '来源 URL'),
617
+ num('tabId', '标签页 ID'),
618
+ json('payload', '事件内容'),
619
+ num('id', 'ID'),
620
+ dt('createdAt', '创建时间'),
621
+ ],
622
+ },
623
+ {
624
+ key: 'raw_snapshots',
625
+ className: 'RawSnapshot',
626
+ label: '原始快照',
627
+ direction: 'push_only',
628
+ description: '采集的原始页面/文件快照(HTML/PDF/Excel,只备份,不回灌)',
629
+ list: { titleField: 'snapshotType', subtitleField: 'status', timeField: 'createdAt' },
630
+ detail: {
631
+ sections: [
632
+ { title: '基本信息', fields: ['snapshotType', 'status', 'ignoreReason'] },
633
+ { title: '来源', fields: ['source'] },
634
+ { title: '内容', fields: ['content'] },
635
+ { title: '系统', fields: ['id', 'createdAt', 'updatedAt'] },
636
+ ],
637
+ },
638
+ fields: [
639
+ ts('snapshotType', '快照类型'),
640
+ {
641
+ name: 'status',
642
+ label: '状态',
643
+ kind: 'enum',
644
+ enumLabels: { pending: '待解析', parsed: '已解析', failed: '失败', ignored: '已忽略' },
645
+ },
646
+ { ...ts('ignoreReason', '忽略原因'), long: true },
647
+ json('source', '来源'),
648
+ json('content', '内容'),
649
+ ts('id', 'ID'),
650
+ dt('createdAt', '创建时间'),
651
+ dt('updatedAt', '更新时间'),
652
+ ],
653
+ },
654
+ ];
655
+ /** key → 定义 索引 */
656
+ exports.SYNC_ENTITY_MAP = Object.fromEntries(exports.SYNC_ENTITIES.map((d) => [d.key, d]));
657
+ function getSyncEntity(key) {
658
+ return exports.SYNC_ENTITY_MAP[key];
659
+ }
660
+ function isSyncEntityKey(key) {
661
+ return key in exports.SYNC_ENTITY_MAP;
662
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './entities';
3
+ export * from './pick';
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./entities"), exports);
19
+ __exportStar(require("./pick"), exports);
package/dist/pick.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * payload 取值工具:支持点路径(如 identity.displayName),
3
+ * 供左列表标题/副标题与详情分区渲染共用。
4
+ */
5
+ export declare function pickDisplay(payload: Record<string, unknown> | null | undefined, path: string): unknown;
6
+ /** 标题兜底:对象值时尝试常见展示子字段,最终回落到 undefined(前端再退回 clientRef) */
7
+ export declare function pickTitle(payload: Record<string, unknown> | null | undefined, path: string): string | undefined;
package/dist/pick.js ADDED
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pickDisplay = pickDisplay;
4
+ exports.pickTitle = pickTitle;
5
+ /**
6
+ * payload 取值工具:支持点路径(如 identity.displayName),
7
+ * 供左列表标题/副标题与详情分区渲染共用。
8
+ */
9
+ function pickDisplay(payload, path) {
10
+ if (!payload || !path)
11
+ return undefined;
12
+ let cur = payload;
13
+ for (const seg of path.split('.')) {
14
+ if (cur == null || typeof cur !== 'object')
15
+ return undefined;
16
+ cur = cur[seg];
17
+ }
18
+ return cur;
19
+ }
20
+ /** 标题兜底:对象值时尝试常见展示子字段,最终回落到 undefined(前端再退回 clientRef) */
21
+ function pickTitle(payload, path) {
22
+ const v = pickDisplay(payload, path);
23
+ if (v == null)
24
+ return undefined;
25
+ if (typeof v === 'string')
26
+ return v || undefined;
27
+ if (typeof v === 'number' || typeof v === 'boolean')
28
+ return String(v);
29
+ if (Array.isArray(v))
30
+ return v.length ? pickTitle({ item: v[0] }, 'item') : undefined;
31
+ if (typeof v === 'object') {
32
+ const o = v;
33
+ for (const k of ['displayName', 'name', 'title', 'value', 'canonicalName', 'domain', 'ruleId', 'dimension']) {
34
+ const sub = pickTitle(o, k);
35
+ if (sub)
36
+ return sub;
37
+ }
38
+ }
39
+ return undefined;
40
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 同步实体元数据类型(FID-098-hm-data-sync iter3)
3
+ *
4
+ * 纯声明式、零框架依赖:hunter-mate(server SYNC_REGISTRY)、dev-ops(backend 校验 +
5
+ * web 左列表右详情页)双端消费。新增同步实体 = 在 entities.ts 加一条定义,
6
+ * 两端布局/菜单/校验自动生效 —— 消灭双端各自维护实体清单的漂移面。
7
+ */
8
+ export type SyncDirection = 'both' | 'push_only';
9
+ export type SyncFieldKind = 'text' | 'number' | 'boolean' | 'datetime' | 'enum' | 'json';
10
+ export interface SyncFieldDef {
11
+ /** payload 字段名(与 TypeORM 实体属性同名,camelCase) */
12
+ name: string;
13
+ /** 中文标签(UI 展示) */
14
+ label: string;
15
+ kind: SyncFieldKind;
16
+ /** kind=enum 时的取值中文映射 */
17
+ enumLabels?: Record<string, string>;
18
+ /** 长文本(详情页等宽展示) */
19
+ long?: boolean;
20
+ }
21
+ export interface SyncEntityDef {
22
+ /**
23
+ * 同步实体键 = TypeORM 表名(hm_sync_items.entity 即此值)。
24
+ * hunter-mate 侧 SYNC_REGISTRY 通过它把元数据接到实体类。
25
+ */
26
+ key: string;
27
+ /** 实体类名(溯源用;hunter-mate server 的类映射以此为索引) */
28
+ className: string;
29
+ /** 中文名(菜单/页面标题) */
30
+ label: string;
31
+ direction: SyncDirection;
32
+ /** 一句话说明(菜单 tooltip / 页面副标题) */
33
+ description?: string;
34
+ /** 左列表布局:从 payload 取哪些字段做标题/副标题/时间 */
35
+ list: {
36
+ titleField: string;
37
+ subtitleField?: string;
38
+ timeField: string;
39
+ };
40
+ /** 右详情布局:分区渲染 payload 字段(未列出的字段由前端以 JSON 附加区兜底) */
41
+ detail: {
42
+ sections: Array<{
43
+ title: string;
44
+ fields: string[];
45
+ }>;
46
+ };
47
+ fields: SyncFieldDef[];
48
+ }
49
+ export declare const isPushOnly: (def: SyncEntityDef) => boolean;
package/dist/types.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * 同步实体元数据类型(FID-098-hm-data-sync iter3)
4
+ *
5
+ * 纯声明式、零框架依赖:hunter-mate(server SYNC_REGISTRY)、dev-ops(backend 校验 +
6
+ * web 左列表右详情页)双端消费。新增同步实体 = 在 entities.ts 加一条定义,
7
+ * 两端布局/菜单/校验自动生效 —— 消灭双端各自维护实体清单的漂移面。
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.isPushOnly = void 0;
11
+ const isPushOnly = (def) => def.direction === 'push_only';
12
+ exports.isPushOnly = isPushOnly;
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@hunter-mate/sync-entities",
3
+ "version": "0.1.0",
4
+ "description": "hunter-mate 数据同步实体定义 SSOT:实体元数据 + 左列表右详情布局配置,hunter-mate 与 dev-ops 双端消费(FID-098-hm-data-sync iter3)",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/hiredchina-com/hunter-mate.git",
9
+ "directory": "packages/sync-entities"
10
+ },
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "devDependencies": {
17
+ "typescript": "^5.4.0"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "typecheck": "tsc --noEmit"
22
+ }
23
+ }