media-library-schema.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. // 公司媒体资源库字段 schema(结构化自《媒体资源库及来源》/ media_library_template.xlsx 表头)
  2. // 边界:仅沉淀字段定义 + 建表参考 + 选号查询映射,供未来建库/开发查询接口时直接消费,不在此建库。
  3. // 与 pgy-schema(蒲公英采集侧)互补:pgy 是「爬虫采集的原始博主数据」,这里是「人工维护 + 商单交付侧」资源库,
  4. // 两者通过 nickname / blogger_id / fans_count / 报价 / 互动率 等字段对账打通。
  5. // 字段表(字段名 → 中文/类型/是否必填/选号维度)。
  6. // 必填字段带 required: true,对应表头里「*」标记(平台/内容类型/达人昵称/粉丝量/主页链接/报价链接/粉丝画像/含税报价)。
  7. const CORE_FIELDS = {
  8. platform: { zh: '平台', type: 'string', required: true, dimension: 'platform', note: '小红书 / 抖音 / B站 等' },
  9. content_type: { zh: '内容类型', type: 'string', required: true, dimension: 'contentType', note: '图文 / 视频 / 直播' },
  10. nickname: { zh: '达人昵称', type: 'string', required: true, dimension: 'id' },
  11. blogger_id: { zh: '达人ID', type: 'string', dimension: 'id', note: '平台侧唯一 ID,用于与蒲公英库对账' },
  12. fans_count: { zh: '粉丝量', type: 'int', required: true, dimension: 'fanRange' },
  13. homepage_url: { zh: '主页链接', type: 'string', required: true, dimension: 'profileUrl' },
  14. quote_url: { zh: '报价链接', type: 'string', required: true, dimension: 'price' },
  15. avg_play_30d: { zh: '近30天均播', type: 'int', dimension: 'interaction' },
  16. fans_profile: { zh: '粉丝画像', type: 'string', required: true, dimension: 'fansProfile', note: '年龄段 / 性别 / 城市 描述' },
  17. tax_inclusive_price: { zh: '含税报价', type: 'number', required: true, dimension: 'price', unit: '元' },
  18. schedule: { zh: '档期', type: 'string', dimension: 'availability', note: '可合作档期' },
  19. case_url: { zh: '代表案例链接', type: 'string', dimension: 'evidence' },
  20. report_form: { zh: '报备形式', type: 'string', dimension: 'commercial', note: '报备 / 非报备' },
  21. picture_price: { zh: '图文价格', type: 'number', dimension: 'price', unit: '元' },
  22. video_price: { zh: '视频价格', type: 'number', dimension: 'price', unit: '元' },
  23. cpv: { zh: 'CPV(单次播放成本)', type: 'number', dimension: 'cpm', unit: '元' },
  24. cpe: { zh: 'CPE(单次互动成本)', type: 'number', dimension: 'cpm', unit: '元' },
  25. read_median: { zh: '阅读中位数', type: 'number', dimension: 'cpm' },
  26. interaction_median: { zh: '互动中位数', type: 'number', dimension: 'interaction' },
  27. style: { zh: '风格', type: 'string', dimension: 'verticality' },
  28. recent_status: { zh: '近况', type: 'string', dimension: 'activity' },
  29. tier: { zh: 'tier层级', type: 'string', dimension: 'tier', note: '头部/腰部/尾部 KOL / KOC' },
  30. verticality: { zh: '垂直度', type: 'number', dimension: 'verticality', unit: '%' },
  31. commercial_ratio: { zh: '商业内容占比', type: 'number', dimension: 'commercial', unit: '%' },
  32. interaction_rate: { zh: '互动率', type: 'number', dimension: 'interaction', unit: '%' },
  33. completion_rate: { zh: '完播率', type: 'number', dimension: 'completionRate', unit: '%' },
  34. platform_score: { zh: '平台评分', type: 'number', dimension: 'score', unit: '分' },
  35. growth_trend: { zh: '增长趋势', type: 'string', dimension: 'growth' },
  36. fan_club_count: { zh: '粉丝团人数', type: 'int', dimension: 'fansProfile' },
  37. live_avg_online: { zh: '直播场均在线', type: 'int', dimension: 'live' },
  38. charging_count: { zh: '充电人数', type: 'int', dimension: 'live' },
  39. avg_like_count: { zh: '平均赞同数', type: 'int', dimension: 'interaction' },
  40. verified: { zh: '认证', type: 'string', dimension: 'verified' },
  41. update_frequency: { zh: '更新频率', type: 'string', dimension: 'activity' },
  42. remark: { zh: '备注', type: 'string', dimension: 'remark' }
  43. };
  44. // 必填字段(表头「*」标记,入库时缺一不可)
  45. const REQUIRED_FIELDS = Object.keys(CORE_FIELDS).filter(key => CORE_FIELDS[key].required);
  46. // 商单交付侧字段(蒲公英爬虫采不到、需人工补录的字段)
  47. const MANUAL_ONLY_FIELDS = [
  48. 'schedule', 'case_url', 'report_form', 'tax_inclusive_price',
  49. 'style', 'recent_status', 'tier', 'verified', 'update_frequency', 'remark'
  50. ];
  51. // 与蒲公英采集库可对账的字段(公司库字段 → 蒲公英字段,用于两库打通去重)
  52. const PGY_ALIAS = {
  53. blogger_id: 'blogger_id',
  54. nickname: 'nickname',
  55. fans_count: 'follower_count',
  56. picture_price: 'picture_price',
  57. video_price: 'video_price',
  58. interaction_rate: 'interaction_rate',
  59. completion_rate: 'video_full_view_rate',
  60. platform_score: 'credit_level',
  61. verticality: 'content_tags',
  62. commercial_ratio: 'co_note_count',
  63. read_median: 'read_midnum',
  64. interaction_median: 'interact_midnum'
  65. };
  66. // 建议索引(选号查询热路径)
  67. const INDEXES = [
  68. { name: 'uk_blogger_id', spec: { platform: 1, blogger_id: 1 }, options: { unique: true } },
  69. { name: 'idx_platform_fans_desc', spec: { platform: 1, fans_count: -1 }, options: {} },
  70. { name: 'idx_platform_tier', spec: { platform: 1, tier: 1, fans_count: -1 }, options: {} },
  71. { name: 'idx_platform_price_asc', spec: { platform: 1, tax_inclusive_price: 1 }, options: {} },
  72. { name: 'idx_platform_interaction_desc', spec: { platform: 1, interaction_rate: -1 }, options: {} },
  73. { name: 'idx_platform_verticality', spec: { platform: 1, verticality: -1 }, options: {} },
  74. { name: 'idx_platform_cpe_asc', spec: { platform: 1, cpe: 1, cpv: 1 }, options: {} }
  75. ];
  76. // 生成 MongoDB 建表 + 索引 DDL 文本
  77. function buildDdl(collectionName = 'media_library') {
  78. const lines = [];
  79. lines.push('// 公司媒体资源库 collection(MongoDB)—— 由 media-library-schema 生成,未来建库直接执行');
  80. lines.push(`db.createCollection('${collectionName}');`);
  81. lines.push('');
  82. for (const index of INDEXES) {
  83. lines.push(`db.${collectionName}.createIndex(${JSON.stringify(index.spec)}, ${JSON.stringify({ name: index.name, ...index.options })});`);
  84. }
  85. lines.push('');
  86. lines.push('// 必填字段(缺一不可,对应模板「*」标记):');
  87. lines.push(`// ${REQUIRED_FIELDS.join(', ')}`);
  88. return lines.join('\n');
  89. }
  90. // 生成选号查询映射 markdown
  91. function buildQueryMap() {
  92. const lines = [];
  93. lines.push('# 公司媒体资源库字段映射');
  94. lines.push('');
  95. lines.push('> 由 media-library-schema 生成。公司侧资源库(人工维护 + 商单交付)字段说明。');
  96. lines.push('');
  97. lines.push(`- 字段总数:${Object.keys(CORE_FIELDS).length},必填 ${REQUIRED_FIELDS.length} 个。`);
  98. lines.push('');
  99. lines.push('## 必填字段(模板「*」标记)');
  100. lines.push('');
  101. lines.push('| 字段 | 中文 | 类型 | 选号维度 |');
  102. lines.push('| --- | --- | --- | --- |');
  103. for (const key of REQUIRED_FIELDS) {
  104. const field = CORE_FIELDS[key];
  105. lines.push(`| \`${key}\` | ${field.zh} | ${field.type}${field.unit ? `(${field.unit})` : ''} | ${field.dimension} |`);
  106. }
  107. lines.push('');
  108. lines.push('## 商单交付侧字段(需人工补录,蒲公英爬不到)');
  109. lines.push('');
  110. lines.push(`\`${MANUAL_ONLY_FIELDS.join('`、`')}\``);
  111. lines.push('');
  112. lines.push('## 与蒲公英采集库字段对账');
  113. lines.push('');
  114. lines.push('| 公司资源库字段 | 蒲公英字段 | 说明 |');
  115. lines.push('| --- | --- | --- |');
  116. for (const [local, pgy] of Object.entries(PGY_ALIAS)) {
  117. lines.push(`| \`${local}\` | \`${pgy}\` | ${CORE_FIELDS[local].zh} |`);
  118. }
  119. return lines.join('\n');
  120. }
  121. module.exports = {
  122. CORE_FIELDS,
  123. REQUIRED_FIELDS,
  124. MANUAL_ONLY_FIELDS,
  125. PGY_ALIAS,
  126. INDEXES,
  127. buildDdl,
  128. buildQueryMap
  129. };