| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- // 公司媒体资源库字段 schema(结构化自《媒体资源库及来源》/ media_library_template.xlsx 表头)
- // 边界:仅沉淀字段定义 + 建表参考 + 选号查询映射,供未来建库/开发查询接口时直接消费,不在此建库。
- // 与 pgy-schema(蒲公英采集侧)互补:pgy 是「爬虫采集的原始博主数据」,这里是「人工维护 + 商单交付侧」资源库,
- // 两者通过 nickname / blogger_id / fans_count / 报价 / 互动率 等字段对账打通。
- // 字段表(字段名 → 中文/类型/是否必填/选号维度)。
- // 必填字段带 required: true,对应表头里「*」标记(平台/内容类型/达人昵称/粉丝量/主页链接/报价链接/粉丝画像/含税报价)。
- const CORE_FIELDS = {
- platform: { zh: '平台', type: 'string', required: true, dimension: 'platform', note: '小红书 / 抖音 / B站 等' },
- content_type: { zh: '内容类型', type: 'string', required: true, dimension: 'contentType', note: '图文 / 视频 / 直播' },
- nickname: { zh: '达人昵称', type: 'string', required: true, dimension: 'id' },
- blogger_id: { zh: '达人ID', type: 'string', dimension: 'id', note: '平台侧唯一 ID,用于与蒲公英库对账' },
- fans_count: { zh: '粉丝量', type: 'int', required: true, dimension: 'fanRange' },
- homepage_url: { zh: '主页链接', type: 'string', required: true, dimension: 'profileUrl' },
- quote_url: { zh: '报价链接', type: 'string', required: true, dimension: 'price' },
- avg_play_30d: { zh: '近30天均播', type: 'int', dimension: 'interaction' },
- fans_profile: { zh: '粉丝画像', type: 'string', required: true, dimension: 'fansProfile', note: '年龄段 / 性别 / 城市 描述' },
- tax_inclusive_price: { zh: '含税报价', type: 'number', required: true, dimension: 'price', unit: '元' },
- schedule: { zh: '档期', type: 'string', dimension: 'availability', note: '可合作档期' },
- case_url: { zh: '代表案例链接', type: 'string', dimension: 'evidence' },
- report_form: { zh: '报备形式', type: 'string', dimension: 'commercial', note: '报备 / 非报备' },
- picture_price: { zh: '图文价格', type: 'number', dimension: 'price', unit: '元' },
- video_price: { zh: '视频价格', type: 'number', dimension: 'price', unit: '元' },
- cpv: { zh: 'CPV(单次播放成本)', type: 'number', dimension: 'cpm', unit: '元' },
- cpe: { zh: 'CPE(单次互动成本)', type: 'number', dimension: 'cpm', unit: '元' },
- read_median: { zh: '阅读中位数', type: 'number', dimension: 'cpm' },
- interaction_median: { zh: '互动中位数', type: 'number', dimension: 'interaction' },
- style: { zh: '风格', type: 'string', dimension: 'verticality' },
- recent_status: { zh: '近况', type: 'string', dimension: 'activity' },
- tier: { zh: 'tier层级', type: 'string', dimension: 'tier', note: '头部/腰部/尾部 KOL / KOC' },
- verticality: { zh: '垂直度', type: 'number', dimension: 'verticality', unit: '%' },
- commercial_ratio: { zh: '商业内容占比', type: 'number', dimension: 'commercial', unit: '%' },
- interaction_rate: { zh: '互动率', type: 'number', dimension: 'interaction', unit: '%' },
- completion_rate: { zh: '完播率', type: 'number', dimension: 'completionRate', unit: '%' },
- platform_score: { zh: '平台评分', type: 'number', dimension: 'score', unit: '分' },
- growth_trend: { zh: '增长趋势', type: 'string', dimension: 'growth' },
- fan_club_count: { zh: '粉丝团人数', type: 'int', dimension: 'fansProfile' },
- live_avg_online: { zh: '直播场均在线', type: 'int', dimension: 'live' },
- charging_count: { zh: '充电人数', type: 'int', dimension: 'live' },
- avg_like_count: { zh: '平均赞同数', type: 'int', dimension: 'interaction' },
- verified: { zh: '认证', type: 'string', dimension: 'verified' },
- update_frequency: { zh: '更新频率', type: 'string', dimension: 'activity' },
- remark: { zh: '备注', type: 'string', dimension: 'remark' }
- };
- // 必填字段(表头「*」标记,入库时缺一不可)
- const REQUIRED_FIELDS = Object.keys(CORE_FIELDS).filter(key => CORE_FIELDS[key].required);
- // 商单交付侧字段(蒲公英爬虫采不到、需人工补录的字段)
- const MANUAL_ONLY_FIELDS = [
- 'schedule', 'case_url', 'report_form', 'tax_inclusive_price',
- 'style', 'recent_status', 'tier', 'verified', 'update_frequency', 'remark'
- ];
- // 与蒲公英采集库可对账的字段(公司库字段 → 蒲公英字段,用于两库打通去重)
- const PGY_ALIAS = {
- blogger_id: 'blogger_id',
- nickname: 'nickname',
- fans_count: 'follower_count',
- picture_price: 'picture_price',
- video_price: 'video_price',
- interaction_rate: 'interaction_rate',
- completion_rate: 'video_full_view_rate',
- platform_score: 'credit_level',
- verticality: 'content_tags',
- commercial_ratio: 'co_note_count',
- read_median: 'read_midnum',
- interaction_median: 'interact_midnum'
- };
- // 建议索引(选号查询热路径)
- const INDEXES = [
- { name: 'uk_blogger_id', spec: { platform: 1, blogger_id: 1 }, options: { unique: true } },
- { name: 'idx_platform_fans_desc', spec: { platform: 1, fans_count: -1 }, options: {} },
- { name: 'idx_platform_tier', spec: { platform: 1, tier: 1, fans_count: -1 }, options: {} },
- { name: 'idx_platform_price_asc', spec: { platform: 1, tax_inclusive_price: 1 }, options: {} },
- { name: 'idx_platform_interaction_desc', spec: { platform: 1, interaction_rate: -1 }, options: {} },
- { name: 'idx_platform_verticality', spec: { platform: 1, verticality: -1 }, options: {} },
- { name: 'idx_platform_cpe_asc', spec: { platform: 1, cpe: 1, cpv: 1 }, options: {} }
- ];
- // 生成 MongoDB 建表 + 索引 DDL 文本
- function buildDdl(collectionName = 'media_library') {
- const lines = [];
- lines.push('// 公司媒体资源库 collection(MongoDB)—— 由 media-library-schema 生成,未来建库直接执行');
- lines.push(`db.createCollection('${collectionName}');`);
- lines.push('');
- for (const index of INDEXES) {
- lines.push(`db.${collectionName}.createIndex(${JSON.stringify(index.spec)}, ${JSON.stringify({ name: index.name, ...index.options })});`);
- }
- lines.push('');
- lines.push('// 必填字段(缺一不可,对应模板「*」标记):');
- lines.push(`// ${REQUIRED_FIELDS.join(', ')}`);
- return lines.join('\n');
- }
- // 生成选号查询映射 markdown
- function buildQueryMap() {
- const lines = [];
- lines.push('# 公司媒体资源库字段映射');
- lines.push('');
- lines.push('> 由 media-library-schema 生成。公司侧资源库(人工维护 + 商单交付)字段说明。');
- lines.push('');
- lines.push(`- 字段总数:${Object.keys(CORE_FIELDS).length},必填 ${REQUIRED_FIELDS.length} 个。`);
- lines.push('');
- lines.push('## 必填字段(模板「*」标记)');
- lines.push('');
- lines.push('| 字段 | 中文 | 类型 | 选号维度 |');
- lines.push('| --- | --- | --- | --- |');
- for (const key of REQUIRED_FIELDS) {
- const field = CORE_FIELDS[key];
- lines.push(`| \`${key}\` | ${field.zh} | ${field.type}${field.unit ? `(${field.unit})` : ''} | ${field.dimension} |`);
- }
- lines.push('');
- lines.push('## 商单交付侧字段(需人工补录,蒲公英爬不到)');
- lines.push('');
- lines.push(`\`${MANUAL_ONLY_FIELDS.join('`、`')}\``);
- lines.push('');
- lines.push('## 与蒲公英采集库字段对账');
- lines.push('');
- lines.push('| 公司资源库字段 | 蒲公英字段 | 说明 |');
- lines.push('| --- | --- | --- |');
- for (const [local, pgy] of Object.entries(PGY_ALIAS)) {
- lines.push(`| \`${local}\` | \`${pgy}\` | ${CORE_FIELDS[local].zh} |`);
- }
- return lines.join('\n');
- }
- module.exports = {
- CORE_FIELDS,
- REQUIRED_FIELDS,
- MANUAL_ONLY_FIELDS,
- PGY_ALIAS,
- INDEXES,
- buildDdl,
- buildQueryMap
- };
|