lactic-v2-ch2-migrate.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // ==============================================================================
  2. // 乳酸菌 v2 · Ch2 成人→儿童 · 机会迁移(Step 3)
  3. // ==============================================================================
  4. // 2.1 成人 VOC 中的儿童信号扫描 · "妈妈/小孩/儿童"
  5. // 2.2 家长购买决策路径 · 双决策者模型(家长买×孩子吃)
  6. // 2.3 可迁移 vs 不可迁移 · 机会矩阵
  7. // ==============================================================================
  8. const LA = require('./lactic-analyze.js');
  9. const C = require('./lactic-components.js');
  10. const stripTags = s => String(s || '').replace(/#[^\s#]+/g, '').trim();
  11. const { BRAND, esc, renderSectionHead, renderVocCard, renderVocGrid,
  12. renderStatStrip, renderMatrix2x2, renderCompareTable, renderInsightHero,
  13. renderFindingCard, renderDecisionList } = C;
  14. // ============================================================
  15. // 2.1 成人 VOC 中的儿童信号扫描
  16. // ============================================================
  17. function renderSub1({ chapter: c, subIdx, sub, slideNum, slideTotal, data, meta }) {
  18. const items = data?.items || [];
  19. // 筛选含"妈妈给孩子买"信号的 VOC
  20. const childSignalRe = /宝宝|小孩|孩子|女儿|儿子|闺女|给娃|幼儿园|入园|换季|积食|挑食|不爱吃饭|喂药/i;
  21. const childSignalItems = items.filter(it => childSignalRe.test(it.content || ''));
  22. // 按信号类型分组
  23. const signalTypes = [
  24. { name: '喂养难题', re: /挑食|不爱吃|喂药|难喂|吐出|抗拒/i, color: BRAND.CORAL },
  25. { name: '肠道症状', re: /腹泻|便秘|拉肚子|积食|消化|胃胀/i, color: BRAND.WARM_ORANGE },
  26. { name: '入园/换季生病', re: /入园|幼儿园|换季|感冒|生病|反复/i, color: BRAND.SKY_BLUE },
  27. { name: '抗生素后恢复', re: /抗生素|消炎|吃药后|用药后/i, color: BRAND.PURPLE },
  28. { name: '营养吸收', re: /吸收|营养|不长|偏瘦|光吃不长/i, color: BRAND.JIANG_GREEN },
  29. ];
  30. const signalStats = signalTypes.map(st => ({
  31. ...st,
  32. count: childSignalItems.filter(it => st.re.test(it.content || '')).length,
  33. }));
  34. const childProbRe = /益生菌|乳酸菌|肠|便秘|腹泻|消化|积食|拉肚|吸收|喂药|抗生素/i;
  35. const substantiveChild = childSignalItems.filter(it => stripTags(it.content).length >= 30 && childProbRe.test(stripTags(it.content)));
  36. const evidence = LA.getEvidence(substantiveChild, { top: 4, seed: 21, minChars: 20 });
  37. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  38. <div class="section-inner">
  39. ${renderSectionHead({
  40. eyebrow: `${c.num}.${subIdx} · CHILD SIGNAL SCAN`,
  41. title: sub,
  42. subtitle: '王总要求:"成人 VOC 中能不能扒拉出来跟儿童相关的?"',
  43. color: c.color,
  44. })}
  45. ${renderStatStrip([
  46. { num: items.length, label: '全部 VOC', color: 'var(--text-3)' },
  47. { num: childSignalItems.length, label: '含儿童信号', color: c.color },
  48. { num: Math.round(childSignalItems.length / Math.max(items.length, 1) * 100) + '%', label: '信号占比', color: c.color },
  49. ])}
  50. <div style="display:grid; grid-template-columns:repeat(5, 1fr); gap:12px; margin-top:24px;">
  51. ${signalStats.map(st => `
  52. <div style="padding:16px; background:${st.color}08; border:1px solid ${st.color}33; border-radius:var(--radius-sm); text-align:center;">
  53. <div style="font-family:var(--font-head); font-size:1.8rem; font-weight:800; color:${st.color};">${st.count}</div>
  54. <div style="color:var(--text-2); font-size:0.78rem; margin-top:4px;">${esc(st.name)}</div>
  55. </div>`).join('')}
  56. </div>
  57. <div style="margin-top:24px;">
  58. <div class="text-mono text-xs" style="color:${c.color}; letter-spacing:0.15em; margin-bottom:14px; font-weight:700;">儿童信号 VOC 原声</div>
  59. ${renderVocGrid(evidence, { columns: 2, accent: c.color })}
  60. </div>
  61. ${renderFindingCard({
  62. icon: '🔍',
  63. title: '信号扫描结论:肠道症状 + 喂养难题是最强儿童信号',
  64. body: '成人 VOC 中的儿童信号集中在两大方向:① <strong>肠道症状</strong>(腹泻/便秘/积食)——与成人需求直接重叠;② <strong>喂养难题</strong>(挑食/喂药难/抗拒)——这是儿童独有的痛点,也是咀嚼片剂型的核心优势所在。',
  65. accent: c.color,
  66. })}
  67. </div>
  68. </section>`;
  69. }
  70. // ============================================================
  71. // 2.2 家长购买决策路径 · 双决策者模型
  72. // ============================================================
  73. function renderSub2({ chapter: c, subIdx, sub, slideNum, slideTotal, data, meta }) {
  74. const items = data?.items || [];
  75. // 家长决策关键词
  76. const decisionFactors = [
  77. { factor: '安全性/成分', re: /安全|成分|无添加|天然|无糖|副作用/i, icon: '🛡️' },
  78. { factor: '效果可感知', re: /有效|管用|见效|好转|改善/i, icon: '✅' },
  79. { factor: '孩子接受度', re: /爱吃|好吃|口味|主动要|像奶片|像糖/i, icon: '😋' },
  80. { factor: '医生/专家推荐', re: /医生|儿科|推荐|开的|处方/i, icon: '👨‍⚕️' },
  81. { factor: '品牌信任', re: /品牌|江中|大牌|老牌|信任/i, icon: '🏷️' },
  82. { factor: '价格/性价比', re: /价格|便宜|贵|性价比|划算/i, icon: '💰' },
  83. ];
  84. const factorStats = decisionFactors.map(df => ({
  85. ...df,
  86. count: items.filter(it => df.re.test(it.content || '')).length,
  87. })).sort((a, b) => b.count - a.count);
  88. // 家长端 VOC:关注安全/成分/效果
  89. const parentRe = /安全|成分|副作用|医生|推荐|品牌|信任|放心|担心|焦虑/i;
  90. const parentItems = items.filter(it => parentRe.test(it.content || ''));
  91. const substParent = parentItems.filter(it => stripTags(it.content).length >= 30);
  92. const parentEvidence = LA.getEvidence(substParent, { top: 2, seed: 221, minChars: 20, contentMatch: /益生菌|乳酸菌|肠|便秘|消化|菌|拉肚/i });
  93. // 孩子端 VOC:关注口味/喂药难
  94. const childExpRe = /好吃|爱吃|像糖|像奶片|口味|不苦|酸甜|嚼|主动要|喂药难|不肯吃|吐出/i;
  95. const childExpItems = items.filter(it => childExpRe.test(it.content || ''));
  96. const substChild = childExpItems.filter(it => stripTags(it.content).length >= 25);
  97. const childEvidence = LA.getEvidence(substChild, { top: 2, seed: 222, minChars: 20, contentMatch: /益生菌|乳酸菌|肠|便秘|消化|菌|拉肚|好吃|爱吃|喂药|嚼/i });
  98. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  99. <div class="section-inner">
  100. ${renderSectionHead({
  101. eyebrow: `${c.num}.${subIdx} · DUAL DECISION MODEL`,
  102. title: sub,
  103. subtitle: '儿童用药的特殊性:家长做决策(买),孩子做体验(吃)——两端都要满足。',
  104. color: c.color,
  105. })}
  106. <div style="display:grid; grid-template-columns:1fr 60px 1fr; gap:0; margin-top:24px; align-items:center;">
  107. <!-- 家长端 -->
  108. <div style="padding:24px; background:${BRAND.BERRY_PINK}08; border:2px solid ${BRAND.BERRY_PINK}44; border-radius:var(--radius-md);">
  109. <div class="text-mono text-xs" style="color:${BRAND.BERRY_PINK}; letter-spacing:0.15em; margin-bottom:14px; font-weight:700;">👩 家长端 · 决策者</div>
  110. <div style="color:var(--text-1); font-size:0.88rem; line-height:1.8;">
  111. <strong>核心驱动</strong>:孩子生病焦虑 → 搜索/咨询 → 品牌筛选 → 购买<br/>
  112. <strong>决策因素排序</strong>:<br/>
  113. ${factorStats.slice(0, 4).map((f, i) => `${f.icon} ${f.factor}(${f.count} 条 VOC 提及)<br/>`).join('')}
  114. <strong>关键矛盾</strong>:想要"药品级效果" + "食品级安全"
  115. </div>
  116. ${parentEvidence.length > 0 ? `
  117. <div style="margin-top:12px; padding-top:12px; border-top:1px dashed ${BRAND.BERRY_PINK}33;">
  118. <div style="color:${BRAND.BERRY_PINK}; font-size:0.68rem; font-weight:700; margin-bottom:6px;">家长决策 VOC 原声</div>
  119. ${parentEvidence.map(ev => `<div style="padding:6px 8px; background:${BRAND.BERRY_PINK}06; border-left:2px solid ${BRAND.BERRY_PINK}44; border-radius:3px; margin-bottom:5px; font-size:0.72rem; color:var(--text-1); line-height:1.45;">
  120. "${esc((ev.content || '').slice(0, 80))}"
  121. <div style="color:var(--text-3); font-size:0.6rem; margin-top:2px;">—${esc(ev.nickname || ev.platform)} · ❤️${ev.likes || 0}</div>
  122. </div>`).join('')}
  123. </div>` : ''}
  124. </div>
  125. <!-- 连接箭头 -->
  126. <div style="text-align:center; color:${c.color}; font-size:2rem;">→</div>
  127. <!-- 孩子端 -->
  128. <div style="padding:24px; background:${BRAND.SKY_BLUE}08; border:2px solid ${BRAND.SKY_BLUE}44; border-radius:var(--radius-md);">
  129. <div class="text-mono text-xs" style="color:${BRAND.SKY_BLUE}; letter-spacing:0.15em; margin-bottom:14px; font-weight:700;">👶 孩子端 · 体验者</div>
  130. <div style="color:var(--text-1); font-size:0.88rem; line-height:1.8;">
  131. <strong>核心驱动</strong>:好不好吃?像糖还是像药?<br/>
  132. <strong>体验因素排序</strong>:<br/>
  133. 😋 口味口感(像奶片/酸甜/不苦)<br/>
  134. 🎨 外观形状(卡通/颜色/大小)<br/>
  135. 📏 方便服用(不用水/可嚼/便携)<br/>
  136. ⏱️ 服用频次(一天几次?会不会忘?)<br/>
  137. <strong>关键门槛</strong>:孩子不吃 = 再好的产品都白买
  138. </div>
  139. ${childEvidence.length > 0 ? `
  140. <div style="margin-top:12px; padding-top:12px; border-top:1px dashed ${BRAND.SKY_BLUE}33;">
  141. <div style="color:${BRAND.SKY_BLUE}; font-size:0.68rem; font-weight:700; margin-bottom:6px;">孩子体验 VOC 原声</div>
  142. ${childEvidence.map(ev => `<div style="padding:6px 8px; background:${BRAND.SKY_BLUE}06; border-left:2px solid ${BRAND.SKY_BLUE}44; border-radius:3px; margin-bottom:5px; font-size:0.72rem; color:var(--text-1); line-height:1.45;">
  143. "${esc((ev.content || '').slice(0, 80))}"
  144. <div style="color:var(--text-3); font-size:0.6rem; margin-top:2px;">—${esc(ev.nickname || ev.platform)} · ❤️${ev.likes || 0}</div>
  145. </div>`).join('')}
  146. </div>` : ''}
  147. </div>
  148. </div>
  149. ${renderInsightHero({
  150. eyebrow: 'KEY INSIGHT',
  151. title: '乳酸菌素片的"双端优势":OTC 说服家长 + 奶片口感赢得孩子',
  152. subtitle: `王总原话:"一个嚼着吃的调理肠道的产品,或者嚼着吃的一个像奶片一样的养护你肠道的东西。"——这就是双端都满足的产品形态。`,
  153. color: c.color,
  154. })}
  155. </div>
  156. </section>`;
  157. }
  158. // ============================================================
  159. // 2.3 可迁移 vs 不可迁移 · 机会矩阵
  160. // ============================================================
  161. function renderSub3({ chapter: c, subIdx, sub, slideNum, slideTotal, data, meta }) {
  162. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  163. <div class="section-inner">
  164. ${renderSectionHead({
  165. eyebrow: `${c.num}.${subIdx} · MIGRATION MATRIX`,
  166. title: sub,
  167. subtitle: '哪些成人机会可以迁移到儿童?哪些不能?',
  168. color: c.color,
  169. })}
  170. ${renderMatrix2x2({
  171. xLabel: '儿童需求强度',
  172. yLabel: '成人验证程度',
  173. xLow: '儿童需求弱',
  174. xHigh: '儿童需求强',
  175. yLow: '成人验证弱',
  176. yHigh: '成人验证强',
  177. quadrants: {
  178. q1: {
  179. title: '★ 高价值迁移区',
  180. color: BRAND.JIANG_GREEN,
  181. items: [
  182. '<strong>肠道日常调理</strong>(腹泻/便秘/消化不良)',
  183. '<strong>抗生素后菌群恢复</strong>(儿童更高频)',
  184. '<strong>咀嚼片剂型优势</strong>(奶片口感)',
  185. ],
  186. note: '→ 直接迁移,Ch3 交叉验证',
  187. },
  188. q2: {
  189. title: '需转化的机会',
  190. color: BRAND.WARM_ORANGE,
  191. items: [
  192. '慢性胃炎调理(成人为主)',
  193. '酒后/饮食不规律(成人场景)',
  194. ],
  195. note: '→ 不可迁移到儿童',
  196. },
  197. q3: {
  198. title: '低优先级',
  199. color: '#888',
  200. items: [
  201. '旅行腹泻预防',
  202. '减肥/肠道清理',
  203. ],
  204. note: '→ 放弃',
  205. },
  206. q4: {
  207. title: '儿童独有机会',
  208. color: BRAND.BERRY_PINK,
  209. items: [
  210. '<strong>入园/换季反复生病</strong>(儿童独有)',
  211. '<strong>营养吸收差/光吃不长</strong>(杨老师洞察)',
  212. '<strong>"瓶瓶罐罐"替代</strong>(家长焦虑)',
  213. ],
  214. note: '→ Ch3 重点验证',
  215. },
  216. },
  217. accent: c.color,
  218. })}
  219. ${renderDecisionList([
  220. '<strong>可迁移 TOP3</strong>:肠道日常调理 + 抗生素后恢复 + 咀嚼片剂型',
  221. '<strong>不可迁移</strong>:慢性胃炎/酒后调理/减肥清肠(成人专属场景)',
  222. '<strong>儿童独有机会</strong>:入园生病/营养吸收/减少补剂——成人端无验证但儿童端需求强烈',
  223. '→ 下一步 Ch3:用儿童微生态竞品 VOC 反向交叉验证这些机会',
  224. ], { accent: c.color, title: 'MIGRATION VERDICT · 迁移结论' })}
  225. </div>
  226. </section>`;
  227. }
  228. module.exports = { renderSub1, renderSub2, renderSub3 };