// 公司媒体资源库字段 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 };