liver-v2-ch0-landscape.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // ==============================================================================
  2. // 肝纯片 v2 · Ch0 护肝人群全景 · VOC 分类归拢(会议 req B)
  3. // ==============================================================================
  4. // 0.1 护肝问题分类归拢 · 六大类 + 共性需求
  5. // 0.2 人群声量 × 平台分布 × 情感地图
  6. // 0.3 TAM → SAM → SOM · 10 亿目标漏斗
  7. // ==============================================================================
  8. const LA = require('./liver-analyze.js');
  9. const C = require('./liver-components.js');
  10. const { BRAND, esc, renderSectionHead, renderVocGrid, renderStatStrip,
  11. renderBarChart, renderCompareTable, renderInsightHero,
  12. renderFindingCard, renderDecisionList, isEnglish, getTranslation, LIVER_CONTEXT_RE } = C;
  13. // ============================================================
  14. // 0.1 护肝问题分类归拢
  15. // ============================================================
  16. function renderSub1({ chapter: c, subIdx, sub, slideNum, slideTotal, data, meta }) {
  17. const items = data?.items || [];
  18. // 六大类分类(regex 拧紧:避免否定匹配和无关内容)
  19. const categories = [
  20. { name: '酒精性肝损伤', desc: '应酬/酒局/宿醉后护肝需求', re: /应酬|酒局|宿醉|解酒|喝多了|不上头|酒后|酒精肝|喝酒伤肝|喝酒.*肝|酒.*护肝/i, color: BRAND.WINE_RED,
  21. needs: '快速解酒/长期护肝/不伤胃/便携应急', icon: '🍺' },
  22. { name: '脂肪肝/体检异常', desc: '体检发现脂肪肝、转氨酶偏高后的焦虑', re: /脂肪肝|转氨酶|肝功能|B超.*肝|体检.*肝|ALT|AST|谷丙/i, color: BRAND.WARM_ORANGE,
  23. needs: '降转氨酶/逆转脂肪肝/日常长期养护', icon: '📊' },
  24. { name: '熬夜/打工人', desc: '加班熬夜人群的肝脏养护诉求', re: /熬夜.*肝|通宵.*肝|加班.*肝|打工人.*肝|肝.*熬夜|护肝.*熬夜/i, color: BRAND.PURPLE,
  25. needs: '便携/快速/平价/日常化', icon: '🌙' },
  26. { name: '药物性肝损伤', desc: '长期服药(降压/降脂/抗生素)担心伤肝', re: /吃药.*肝|服药.*肝|药物.*肝|伤肝|保肝|药物性肝/i, color: BRAND.CORAL,
  27. needs: '安全护肝/不与药物冲突/医生认可', icon: '💊' },
  28. { name: '送礼/孝心', desc: '给父母/长辈买护肝产品的礼品需求', re: /送.*护肝|护肝.*礼|送.*肝片|礼盒.*肝|孝心.*肝|买给.*爸|买给.*父|送爸|送父|护肝.*送/i, color: BRAND.GOLD,
  29. needs: '包装好看/有面子/品牌信任/安全', icon: '🎁' },
  30. { name: '成分/智商税争议', desc: '对护肝保健品效果的质疑和成分研究', re: /智商税|有用吗|骗人|水飞蓟|奶蓟|DMY|藤茶|靠谱|成分.*肝|护肝.*成分/i, color: BRAND.TECH_BLUE,
  31. needs: '成分透明/科学证据/真实口碑', icon: '🔬' },
  32. ];
  33. const catStats = categories.map((cat, ci) => {
  34. // 双重过滤:类别 regex + 护肝上下文
  35. const matched = items.filter(it => {
  36. const txt = it.content || '';
  37. return cat.re.test(txt) && LIVER_CONTEXT_RE.test(txt);
  38. });
  39. const evidence = LA.getEvidence(matched, { top: 2, seed: 101 + ci, minChars: 15, contentMatch: cat.re });
  40. return { ...cat, count: matched.length, evidence };
  41. }).sort((a, b) => b.count - a.count);
  42. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  43. <div class="section-inner">
  44. ${renderSectionHead({
  45. eyebrow: `${c.num}.${subIdx} · VOC CLASSIFICATION`,
  46. title: sub,
  47. subtitle: '江旭要求:"让 AI 把这么多条整个梳理分类归拢一下,分为哪几大类,有哪些共性需求。"',
  48. color: c.color,
  49. })}
  50. ${renderStatStrip([
  51. { num: items.length, label: '全部 VOC', color: c.color },
  52. { num: catStats.length, label: '大类', color: BRAND.LIVER_AMBER },
  53. { num: Object.keys(meta.platforms).length, label: '平台', color: BRAND.WARM_ORANGE },
  54. { num: meta.keywords, label: '关键词', color: BRAND.PURPLE },
  55. ])}
  56. <div style="display:grid; grid-template-columns:repeat(3, 1fr); gap:16px; margin-top:24px;">
  57. ${catStats.map(cat => `
  58. <div style="padding:20px; background:${cat.color}08; border:1px solid ${cat.color}33; border-top:3px solid ${cat.color}; border-radius:var(--radius-md);">
  59. <div style="display:flex; align-items:center; gap:8px; margin-bottom:10px;">
  60. <span style="font-size:1.3rem;">${cat.icon}</span>
  61. <div>
  62. <div class="text-mono text-xs" style="color:${cat.color}; font-weight:700;">${esc(cat.name)}</div>
  63. <div style="color:var(--text-3); font-size:0.68rem;">${cat.count} 条 VOC</div>
  64. </div>
  65. </div>
  66. <div style="color:var(--text-2); font-size:0.82rem; line-height:1.5; margin-bottom:10px;">${esc(cat.desc)}</div>
  67. <div style="padding-top:10px; border-top:1px dashed ${cat.color}33;">
  68. <div style="color:${cat.color}; font-size:0.74rem; font-weight:700;">共性需求</div>
  69. <div style="color:var(--text-1); font-size:0.82rem; margin-top:4px;">${esc(cat.needs)}</div>
  70. </div>
  71. ${cat.evidence.length > 0 ? `
  72. <div style="margin-top:10px; padding-top:10px; border-top:1px dashed ${cat.color}33;">
  73. <div style="color:${cat.color}; font-size:0.68rem; font-weight:700; margin-bottom:6px;">代表性 VOC 原声</div>
  74. ${cat.evidence.map(ev => {
  75. const raw = (ev.content || '').slice(0, 80);
  76. const trans = isEnglish(raw) ? getTranslation(ev.content) : null;
  77. const display = trans || raw;
  78. const badge = trans ? ' <span style="color:' + BRAND.WARM_ORANGE + ';font-size:0.56rem;">🔄已翻译</span>' : (isEnglish(raw) ? ' <span style="color:' + BRAND.WARM_ORANGE + ';font-size:0.56rem;">EN</span>' : '');
  79. return `<div style="padding:6px 8px; background:${cat.color}06; border-left:2px solid ${cat.color}44; border-radius:3px; margin-bottom:5px; font-size:0.72rem; color:var(--text-1); line-height:1.45;">
  80. "${esc(display)}"${badge}
  81. <div style="color:var(--text-3); font-size:0.6rem; margin-top:2px;">—${esc(ev.nickname || ev.platform)} · ❤️${ev.likes || 0}</div>
  82. </div>`;
  83. }).join('')}
  84. </div>` : ''}
  85. </div>`).join('')}
  86. </div>
  87. ${renderFindingCard({
  88. icon: '💡',
  89. title: '分类归拢核心发现',
  90. body: '护肝人群的问题<strong>不是单一的</strong>——"酒精性"和"脂肪肝"经常重叠,"熬夜"和"药物性"人群高度交叉。但用户的<strong>心理动机</strong>只有两类:<strong style="color:' + BRAND.CORAL + ';">恐惧驱动</strong>(体检异常/医生警告)和 <strong style="color:' + BRAND.JIANG_GREEN + ';">预防驱动</strong>(熬夜/应酬后主动养护)。肝纯片需要同时应对两种心理。',
  91. accent: c.color,
  92. })}
  93. </div>
  94. </section>`;
  95. }
  96. // ============================================================
  97. // 0.2 人群声量 × 平台 × 情感
  98. // ============================================================
  99. function renderSub2({ chapter: c, subIdx, sub, slideNum, slideTotal, data, meta }) {
  100. const items = data?.items || [];
  101. // 平台分布
  102. const platformBars = Object.entries(meta.platforms).map(([pf, count]) => ({
  103. label: ({xhs:'小红书',douyin:'抖音',amazon:'Amazon'})[pf] || pf,
  104. value: count,
  105. color: ({xhs:'#FF2442',douyin:'#1A1A1A',amazon:'#FF9900'})[pf] || '#888',
  106. })).sort((a, b) => b.value - a.value);
  107. // 情感分布
  108. const sentBars = Object.entries(meta.sentiments || {}).map(([s, count]) => ({
  109. label: ({positive:'好评',negative:'差评',neutral:'中性',conflicted:'矛盾'})[s] || s,
  110. value: count,
  111. color: ({positive:BRAND.JIANG_GREEN,negative:BRAND.CORAL,neutral:'#888',conflicted:BRAND.WARM_ORANGE})[s] || '#888',
  112. })).sort((a, b) => b.value - a.value);
  113. // 假设覆盖
  114. const hypBars = Object.entries(meta.hypotheses || {}).map(([h, count]) => {
  115. const hyp = LA.HYPOTHESES[h] || {};
  116. return { label: `${h} ${(hyp.title || '').slice(0, 12)}`, value: count, color: BRAND.PURPLE };
  117. }).sort((a, b) => b.value - a.value);
  118. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  119. <div class="section-inner">
  120. ${renderSectionHead({
  121. eyebrow: `${c.num}.${subIdx} · DATA LANDSCAPE`,
  122. title: sub,
  123. subtitle: '7715 条 VOC 的平台、情感、假设标签全景分布。',
  124. color: c.color,
  125. })}
  126. <div style="display:grid; grid-template-columns:1fr 1fr; gap:24px; margin-top:24px;">
  127. <div>
  128. ${renderBarChart(platformBars, { accent: c.color, title: '平台分布', height: 180 })}
  129. </div>
  130. <div>
  131. ${renderBarChart(sentBars, { accent: c.color, title: '情感分布', height: 180 })}
  132. </div>
  133. </div>
  134. <div style="margin-top:24px;">
  135. ${renderBarChart(hypBars, { accent: BRAND.PURPLE, title: 'H1-H8 假设标签覆盖量', height: 260 })}
  136. </div>
  137. ${renderFindingCard({
  138. icon: '📊',
  139. title: '数据质量评估',
  140. body: `<strong>${items.length}</strong> 条真实 VOC,覆盖 <strong>${Object.keys(meta.platforms).length}</strong> 个平台 × <strong>${meta.keywords}</strong> 个关键词 × <strong>H1-H8</strong> 八条假设。小红书贡献最多声量(种草/分享场景),Amazon 提供国际对标(milk thistle 差评),抖音补充场景化内容。数据底座充足,可支撑结论。`,
  141. accent: c.color,
  142. })}
  143. </div>
  144. </section>`;
  145. }
  146. // ============================================================
  147. // 0.3 TAM → SAM → SOM
  148. // ============================================================
  149. function renderSub3({ chapter: c, subIdx, sub, slideNum, slideTotal, data, meta }) {
  150. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  151. <div class="section-inner">
  152. ${renderSectionHead({
  153. eyebrow: `${c.num}.${subIdx} · TAM → SAM → SOM`,
  154. title: sub,
  155. subtitle: '总经理习惯框架:全中国多少人→发病率→转化率→吃什么。',
  156. color: c.color,
  157. })}
  158. <div style="max-width:800px; margin:0 auto;">
  159. <div style="background:${BRAND.TECH_BLUE}10; border:2px solid ${BRAND.TECH_BLUE}44; border-radius:var(--radius-md); padding:28px 32px; margin-bottom:16px;">
  160. <div class="text-mono text-xs" style="color:${BRAND.TECH_BLUE}; letter-spacing:0.2em; font-weight:700; margin-bottom:8px;">TAM · 品类全市场</div>
  161. <div style="font-family:var(--font-head); font-size:2.4rem; font-weight:800; color:${BRAND.TECH_BLUE};">~150 亿</div>
  162. <div style="color:var(--text-1); font-size:0.92rem; line-height:1.7; margin-top:8px;">
  163. 护肝保健品 <strong>~50 亿</strong>(Swisse/汤臣/葵花/片仔癀)+
  164. OTC 护肝药 <strong>~60 亿</strong>(易善复/水飞蓟片/熊胆粉)+
  165. 解酒品类 <strong>~20 亿</strong> + 护肝食品 <strong>~20 亿</strong>
  166. </div>
  167. </div>
  168. <div style="background:${BRAND.LIVER_AMBER}10; border:2px solid ${BRAND.LIVER_AMBER}44; border-radius:var(--radius-md); padding:28px 32px; margin-bottom:16px; margin-left:40px; margin-right:40px;">
  169. <div class="text-mono text-xs" style="color:${BRAND.LIVER_AMBER}; letter-spacing:0.2em; font-weight:700; margin-bottom:8px;">SAM · 可服务市场</div>
  170. <div style="font-family:var(--font-head); font-size:2.4rem; font-weight:800; color:${BRAND.LIVER_AMBER};">~40 亿</div>
  171. <div style="color:var(--text-1); font-size:0.92rem; line-height:1.7; margin-top:8px;">
  172. 药线护肝保健品 <strong>~12 亿</strong> +
  173. 电商自购护肝 <strong>~10 亿</strong> +
  174. 礼品孝心 <strong>~8 亿</strong> +
  175. 餐饮解酒 <strong>~10 亿</strong>
  176. </div>
  177. </div>
  178. <div style="background:${BRAND.JIANG_GREEN}10; border:2px solid ${BRAND.JIANG_GREEN}44; border-radius:var(--radius-md); padding:28px 32px; margin-left:80px; margin-right:80px;">
  179. <div class="text-mono text-xs" style="color:${BRAND.JIANG_GREEN}; letter-spacing:0.2em; font-weight:700; margin-bottom:8px;">SOM · 可获取市场</div>
  180. <div style="font-family:var(--font-head); font-size:2.4rem; font-weight:800; color:${BRAND.JIANG_GREEN};">10 亿 / 3 年</div>
  181. <div style="color:var(--text-1); font-size:0.92rem; line-height:1.7; margin-top:8px;">
  182. Y1:药线 + 电商 <strong>5 亿</strong>(大参林/一心堂 3w 门店 + 京东自营)<br/>
  183. Y2:礼品 + 餐饮 <strong>3 亿</strong>(新中式礼盒 + 锡纸分食)<br/>
  184. Y3:国际 + 品类升级 <strong>2 亿</strong>(DMY 出海 + 功能饮品)
  185. </div>
  186. </div>
  187. </div>
  188. ${renderFindingCard({
  189. icon: '🎯',
  190. title: '漏斗结论:10 亿目标 = SAM 的 25%,可行但需四渠道并进',
  191. body: '药线 3 亿(30%)+ 礼品 2.5 亿(25%)+ 餐饮 2.5 亿(25%)+ 电商 2 亿(20%)。<strong>藤茶 DMY 差异化</strong>是四渠道通吃的底层武器——药线靠"总黄酮 98%"说服药师,电商靠"千年食饮史"做种草,礼品靠"东方养生"做面子,餐饮靠"真解酒"做体感。',
  192. accent: BRAND.JIANG_GREEN,
  193. })}
  194. </div>
  195. </section>`;
  196. }
  197. module.exports = { renderSub1, renderSub2, renderSub3 };