houguyin-v2-ch3-pain.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. // 江中猴菇饮 v2 · Ch3 痛声地图(4 子章)
  2. // 3.1 化药级痛声 · "有效但不敢停" × PPI依赖焦虑
  3. // 3.2 中成药痛声 · "苦涩难咽年轻人不买" × 代际断层
  4. // 3.3 功能食品痛声 · "吃了个寂寞" × 无OTC信任
  5. // 3.4 跨层汇总 · 5大共性未满足需求 × 声量-满足度四象限
  6. const HGY = require('./houguyin-analyze.js');
  7. const C = require('./houguyin-components.js');
  8. const { BRAND, esc, renderVocCard, renderVocGrid, renderSectionHead, renderMatrix2x2 } = C;
  9. // ================================================================
  10. // 3.1 · 化药级痛声 · "有效但不敢停" × PPI依赖焦虑
  11. // ================================================================
  12. function renderSub1(ctx) {
  13. const { chapter: c, subIdx, slideNum, slideTotal, data } = ctx;
  14. const items = data.items || [];
  15. const vocCards = HGY.getEvidence(items, { hypothesis: 'H4', top: 6, seed: 31 });
  16. // 痛点定义:用 regex 从 VOC 数据中动态计算频次
  17. const painDefs = [
  18. { pain: '有效但不敢停', re: /不敢停|停不了|停不掉|离不开|依赖|一直吃/i, color: '#C62828' },
  19. { pain: '副作用焦虑', re: /副作用|骨质疏松|肾|伤身|长期.*害怕|担心.*身体/i, color: '#E53935' },
  20. { pain: '反弹恐惧', re: /反弹|停.*又|一停就|复发|反复/i, color: BRAND.CORAL },
  21. { pain: '越吃越多', re: /越来越|加量|一粒.*两粒|恶性循环|越吃/i, color: BRAND.WARM_ORANGE },
  22. { pain: '想减不敢减', re: /减量|想停|想减|慢慢.*减|试着.*停/i, color: BRAND.HOUGU_TAN },
  23. ];
  24. // 动态计算:从 H4 相关 VOC 中统计各痛点命中数
  25. const h4Items = items.filter(it => {
  26. const hs = Array.isArray(it.hypothesis) ? it.hypothesis : (it.hypothesis ? [it.hypothesis] : []);
  27. return hs.includes('H4') || hs.includes('H3');
  28. });
  29. const painPoints = painDefs.map(p => {
  30. const count = h4Items.filter(it => p.re.test(it.content || '')).length;
  31. return { ...p, count };
  32. });
  33. const maxCount = Math.max(...painPoints.map(p => p.count), 1);
  34. // 归一化到 60-100 区间,保证视觉效果
  35. painPoints.forEach(p => { p.intensity = Math.round(60 + (p.count / maxCount) * 40); });
  36. painPoints.sort((a, b) => b.count - a.count);
  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} · 痛声地图`,
  41. title: '化药级痛声 · <span style="color:#E53935;">"有效但不敢停"</span>',
  42. subtitle: 'PPI 用户(~8000万人)的核心矛盾:药有效→但形成依赖→想停→一停就反弹→继续吃→焦虑加深。',
  43. color: '#E53935',
  44. })}
  45. <!-- 痛点强度条 -->
  46. <div style="margin-top:24px; padding:20px; background:var(--bg-1); border:1px solid var(--border); border-radius:var(--radius-md);">
  47. <div class="text-mono text-xs" style="color:${BRAND.CORAL}; letter-spacing:0.12em; margin-bottom:16px; font-weight:700;">PPI 用户 5 大痛点 × VOC 频次强度 <span style="color:var(--text-3); font-weight:400;">(基于 ${h4Items.length} 条 H3/H4 相关 VOC 动态计算)</span></div>
  48. ${painPoints.map(p => `
  49. <div style="margin-bottom:14px;">
  50. <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
  51. <span style="font-weight:700; font-size:0.88rem; color:${p.color};">${esc(p.pain)}</span>
  52. <span style="font-family:var(--font-head); font-weight:900; font-size:0.9rem; color:${p.color};">${p.intensity}<span style="font-size:0.65rem; font-weight:400; color:var(--text-3); margin-left:4px;">(${p.count} 条)</span></span>
  53. </div>
  54. <div style="height:8px; background:var(--bg-2); border-radius:4px; overflow:hidden;">
  55. <div style="height:100%; width:${p.intensity}%; background:${p.color}; border-radius:4px;"></div>
  56. </div>
  57. </div>`).join('')}
  58. </div>
  59. <!-- 猴菇饮机会 -->
  60. <div style="margin-top:16px; padding:14px 20px; background:${BRAND.JIANG_GREEN}08; border-left:4px solid ${BRAND.JIANG_GREEN}; border-radius:0 var(--radius-sm) var(--radius-sm) 0;">
  61. <div style="font-size:0.85rem; color:var(--text-1); line-height:1.7;">
  62. <strong style="color:${BRAND.JIANG_GREEN};">猴菇饮切入点 · PPI 减量伴侣</strong>:用户不是想不吃药,是想"安全地减少药量"。猴菇饮如果能定位为<strong>"减量过渡期的温和养护"</strong>,接住的是"想减不敢减"这个高频高痛点。
  63. </div>
  64. </div>
  65. ${vocCards.length > 0 ? `
  66. <div style="margin-top:16px;">
  67. <div class="text-mono text-xs" style="color:var(--text-3); margin-bottom:8px;">VOC 原声 · PPI 依赖用户</div>
  68. <div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px;">
  69. ${vocCards.slice(0, 6).map(v => renderVocCard(v, { compact: true })).join('')}
  70. </div>
  71. </div>` : ''}
  72. </div>
  73. </section>`;
  74. }
  75. // ================================================================
  76. // 3.2 · 中成药痛声 · "苦涩难咽年轻人不买" × 代际断层
  77. // ================================================================
  78. function renderSub2(ctx) {
  79. const { chapter: c, subIdx, slideNum, slideTotal, data } = ctx;
  80. const items = data.items || [];
  81. const vocCards = HGY.getEvidence(items, { hypothesis: 'H3', tag: '口感', top: 4, seed: 37 });
  82. // 动态计算:从 H3 相关 VOC 中统计各痛点命中数
  83. const h3Items = items.filter(it => {
  84. const hs = Array.isArray(it.hypothesis) ? it.hypothesis : (it.hypothesis ? [it.hypothesis] : []);
  85. return hs.includes('H3');
  86. });
  87. const painDefs = [
  88. { pain: '口感差', desc: '苦涩/颗粒大/冲泡麻烦/有中药味', ageGroup: '全年龄',
  89. re: /苦|难喝|味道|口感|中药味|冲泡|颗粒大|难吃|难闻|恶心/i, color: BRAND.CORAL },
  90. { pain: '代际断层', desc: '年轻人认为中成药=老人吃的', ageGroup: '18-35',
  91. re: /老人|年轻|我妈|妈让|爸让|老年人|年纪大|过时/i, color: BRAND.WARM_ORANGE },
  92. { pain: '疗程压力', desc: '需持续服用才有效,大多数人坚持不了', ageGroup: '全年龄',
  93. re: /三个月|坚持|疗程|忘了|中途|没耐心|太久|持续/i, color: BRAND.HOUGU_TAN },
  94. { pain: '效果不确定', desc: '"温和"=不知道有没有效', ageGroup: '25-45',
  95. re: /没效|不知道.*效|有没有用|感觉不到|看不出|不明显/i, color: BRAND.PURPLE },
  96. ];
  97. const painPoints = painDefs.map(p => {
  98. const count = h3Items.filter(it => p.re.test(it.content || '')).length;
  99. return { ...p, count };
  100. });
  101. const maxCount2 = Math.max(...painPoints.map(p => p.count), 1);
  102. painPoints.forEach(p => { p.severity = p.count >= maxCount2 * 0.8 ? '极高' : p.count >= maxCount2 * 0.5 ? '高' : '中高'; });
  103. painPoints.sort((a, b) => b.count - a.count);
  104. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  105. <div class="section-inner">
  106. ${renderSectionHead({
  107. eyebrow: `${c.num}.${subIdx} · 痛声地图`,
  108. title: '中成药痛声 · <span style="color:' + BRAND.WARM_ORANGE + ';">"苦涩难咽"</span> × 代际断层',
  109. subtitle: '三九胃泰/摩罗丹/康复新液的用户(~6000万人):产品有效但体验太差,年轻用户正在流失。',
  110. color: BRAND.WARM_ORANGE,
  111. })}
  112. <div style="margin-top:24px; display:grid; grid-template-columns:1fr 1fr; gap:12px;">
  113. <div class="text-mono text-xs" style="color:${BRAND.WARM_ORANGE}; letter-spacing:0.12em; margin-bottom:4px; font-weight:700; grid-column:1/-1;">中成药 4 大痛声 <span style="color:var(--text-3); font-weight:400;">(基于 ${h3Items.length} 条 H3 VOC 动态统计)</span></div>
  114. ${painPoints.map(p => `
  115. <div style="padding:16px; background:var(--bg-1); border:1px solid var(--border); border-radius:var(--radius-md);">
  116. <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
  117. <span style="font-weight:800; font-size:0.92rem; color:${p.color};">${esc(p.pain)}</span>
  118. <span style="font-size:0.72rem; padding:2px 8px; background:${p.color}10; color:${p.color}; border-radius:10px;">${p.severity} · ${p.count} 条</span>
  119. </div>
  120. <div style="font-size:0.82rem; color:var(--text-1); margin-bottom:6px;">${esc(p.desc)}</div>
  121. <div style="font-size:0.68rem; color:var(--text-3); margin-top:4px;">易感人群:${esc(p.ageGroup)}</div>
  122. </div>`).join('')}
  123. </div>
  124. <div style="margin-top:16px; padding:14px 20px; background:${BRAND.JIANG_GREEN}08; border-left:4px solid ${BRAND.JIANG_GREEN}; border-radius:0 var(--radius-sm) var(--radius-sm) 0;">
  125. <div style="font-size:0.85rem; color:var(--text-1); line-height:1.7;">
  126. <strong style="color:${BRAND.JIANG_GREEN};">猴菇饮切入点 · 代际替代</strong>:猴菇饮天然解决了中成药的四大痛点——<strong>好喝</strong>(vs 苦涩)、<strong>年轻化</strong>(vs 老人药)、<strong>无疗程压力</strong>(随时喝 vs 坚持三个月)、<strong>可感知</strong>(温热液体的即时舒服感 vs 不知道有没效)。
  127. </div>
  128. </div>
  129. ${vocCards.length > 0 ? `
  130. <div style="margin-top:16px;">
  131. <div class="text-mono text-xs" style="color:var(--text-3); margin-bottom:8px;">VOC 原声 · 中成药口感/体验</div>
  132. <div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;">
  133. ${vocCards.slice(0, 4).map(v => renderVocCard(v, { compact: true })).join('')}
  134. </div>
  135. </div>` : ''}
  136. </div>
  137. </section>`;
  138. }
  139. // ================================================================
  140. // 3.3 · 功能食品痛声 · "吃了个寂寞" × 无OTC信任
  141. // ================================================================
  142. function renderSub3(ctx) {
  143. const { chapter: c, subIdx, slideNum, slideTotal, data } = ctx;
  144. const items = data.items || [];
  145. const vocCards = HGY.getEvidence(items, { hypothesis: 'H1', top: 4, seed: 41, contentMatch: /米稀|猴姑|养胃|胃|早餐|暖胃/i });
  146. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  147. <div class="section-inner">
  148. ${renderSectionHead({
  149. eyebrow: `${c.num}.${subIdx} · 痛声地图`,
  150. title: '功能食品痛声 · <span style="color:' + BRAND.HOUGU_TAN + ';">"吃了个寂寞"</span>',
  151. subtitle: '养胃茶/猴头菇保健品/米稀用户(~9000万人):门槛低但信任低,"不知道有没有效"是最大痛点。',
  152. color: BRAND.HOUGU_TAN,
  153. })}
  154. <div style="margin-top:24px; padding:20px; background:var(--bg-1); border:1px solid var(--border); border-radius:var(--radius-md);">
  155. <div class="text-mono text-xs" style="color:${BRAND.HOUGU_TAN}; letter-spacing:0.12em; margin-bottom:16px; font-weight:700;">功能食品 3 大痛声 <span style="color:var(--text-3); font-weight:400;">(痛声为 VOC 归纳合成)</span></div>
  156. <div style="display:flex; flex-direction:column; gap:14px;">
  157. <div style="padding:14px 16px; background:${BRAND.CORAL}05; border-left:4px solid ${BRAND.CORAL}; border-radius:0 var(--radius-sm) var(--radius-sm) 0;">
  158. <div style="font-weight:800; font-size:0.92rem; color:${BRAND.CORAL}; margin-bottom:6px;">痛声 ① · 效果存疑</div>
  159. <div style="font-size:0.82rem; color:var(--text-1); line-height:1.6;">
  160. "吃了个寂寞"——功能食品最大的信任问题。用户知道猴头菇养胃,但吃完没有任何可感知的变化。<br>
  161. <em style="color:var(--text-3);">"猴菇米稀我喝了半年,该胃疼还是胃疼,就是当早餐了"</em>
  162. </div>
  163. </div>
  164. <div style="padding:14px 16px; background:${BRAND.WARM_ORANGE}05; border-left:4px solid ${BRAND.WARM_ORANGE}; border-radius:0 var(--radius-sm) var(--radius-sm) 0;">
  165. <div style="font-weight:800; font-size:0.92rem; color:${BRAND.WARM_ORANGE}; margin-bottom:6px;">痛声 ② · 场景单一</div>
  166. <div style="font-size:0.82rem; color:var(--text-1); line-height:1.6;">
  167. 米稀 = 早餐。一旦换了场景(午后/应酬前/夜宵后),用户不知道能用什么。<br>
  168. <em style="color:var(--text-3);">"晚上加班胃不舒服,不可能冲个米稀吧,而且办公室也没条件"</em>
  169. </div>
  170. </div>
  171. <div style="padding:14px 16px; background:${BRAND.PURPLE}05; border-left:4px solid ${BRAND.PURPLE}; border-radius:0 var(--radius-sm) var(--radius-sm) 0;">
  172. <div style="font-weight:800; font-size:0.92rem; color:${BRAND.PURPLE}; margin-bottom:6px;">痛声 ③ · 身份尴尬</div>
  173. <div style="font-size:0.82rem; color:var(--text-1); line-height:1.6;">
  174. 不是药,说不出"疗效";不是保健品,没有蓝帽子;只是食品,凭什么贵?<br>
  175. <em style="color:var(--text-3);">"花40块买一盒猴头菇饼干养胃?超市里随便买点粗粮不是一样?"</em>
  176. </div>
  177. </div>
  178. </div>
  179. </div>
  180. <div style="margin-top:16px; padding:14px 20px; background:${BRAND.JIANG_GREEN}08; border-left:4px solid ${BRAND.JIANG_GREEN}; border-radius:0 var(--radius-sm) var(--radius-sm) 0;">
  181. <div style="font-size:0.85rem; color:var(--text-1); line-height:1.7;">
  182. <strong style="color:${BRAND.JIANG_GREEN};">猴菇饮的独特解法</strong>:<strong>OTC 身份</strong>直接解决"身份尴尬"(有药品信任);<strong>液态即饮</strong>解决"场景单一"(不需要冲泡/热水/碗);但<strong>"效果存疑"需要通过定位话术而非产品改造来解决</strong>——下文 Ch5 定位推导将回答这个问题。
  183. </div>
  184. </div>
  185. ${vocCards.length > 0 ? `
  186. <div style="margin-top:16px;">
  187. <div class="text-mono text-xs" style="color:var(--text-3); margin-bottom:8px;">VOC 原声 · 功能食品用户</div>
  188. <div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;">
  189. ${vocCards.slice(0, 4).map(v => renderVocCard(v, { compact: true })).join('')}
  190. </div>
  191. </div>` : ''}
  192. </div>
  193. </section>`;
  194. }
  195. // ================================================================
  196. // 3.4 · 跨层汇总 · 5大共性未满足需求 × 声量-满足度四象限
  197. // ================================================================
  198. function renderSub4(ctx) {
  199. const { chapter: c, subIdx, slideNum, slideTotal, data } = ctx;
  200. const items = data.items || [];
  201. // 为四象限每个条目定义 regex,动态计算 VOC 命中数
  202. const q1Defs = [
  203. { label: 'PPI 减量焦虑(想停不敢停)', re: /停不了|不敢停|依赖|减量|想停|反弹/i },
  204. { label: '中成药口感代际断层', re: /苦|难喝|口感|年轻.*不|老人|代际/i },
  205. { label: '日常养护无便捷选择', re: /日常|方便|随时|便捷|办公室|不需要.*冲泡/i },
  206. ];
  207. const q2Defs = [
  208. { label: '急性胃痛缓解(达喜/PPI)', re: /胃痛.*吃|达喜|止痛|急性|立马|马上/i },
  209. { label: '早餐养胃(米稀已占位)', re: /早餐|米稀|早上|粥|冲泡.*早/i },
  210. ];
  211. const q3Defs = [
  212. { label: '术后恢复(处方解决)', re: /手术|术后|住院|恢复期/i },
  213. { label: '幽门螺杆菌治疗(三联/四联)', re: /幽门|螺杆|三联|四联|HP/i },
  214. ];
  215. const q4Defs = [
  216. { label: '糖友养胃(无糖版需求)', re: /糖尿|血糖|无糖|糖友|代糖/i },
  217. { label: '"送胃"礼品场景', re: /送|礼|礼盒|送人|过节|看望/i },
  218. { label: '夜宵族/宵夜后养护', re: /夜宵|宵夜|晚上吃|夜里|加班.*吃/i },
  219. ];
  220. const countItems = (defs) => defs.map(d => ({
  221. ...d, count: items.filter(it => d.re.test(it.content || '')).length,
  222. }));
  223. const q1Items = countItems(q1Defs), q2Items = countItems(q2Defs);
  224. const q3Items = countItems(q3Defs), q4Items = countItems(q4Defs);
  225. const fmtItems = (arr) => arr.map(a => `${a.label}(${a.count} 条)`);
  226. const quadrants = {
  227. q1: { title: '高声量 × 低满足 → 核心机会', color: BRAND.JIANG_GREEN,
  228. items: fmtItems(q1Items),
  229. note: '猴菇饮最应该切的 3 个需求' },
  230. q2: { title: '高声量 × 高满足 → 红海', color: BRAND.TECH_BLUE,
  231. items: fmtItems(q2Items),
  232. note: '不要正面竞争' },
  233. q3: { title: '低声量 × 高满足 → 不值得进', color: '#9E9E9E',
  234. items: fmtItems(q3Items),
  235. note: '处方壁垒' },
  236. q4: { title: '低声量 × 低满足 → 长尾观察', color: BRAND.WARM_ORANGE,
  237. items: fmtItems(q4Items),
  238. note: '规模待验证的差异化机会' },
  239. };
  240. return `<section class="report-section" id="${c.key}-${subIdx}" data-tone="${c.tone}" data-slide-num="${slideNum}/${slideTotal}">
  241. <div class="section-inner">
  242. ${renderSectionHead({
  243. eyebrow: `${c.num}.${subIdx} · 痛声地图`,
  244. title: '跨层汇总 · <span style="color:' + BRAND.PURPLE + ';">5 大共性未满足需求</span> × 四象限',
  245. subtitle: '把 Ch3.1-3.3 的痛声汇总,按"声量(多少人在说)× 满足度(现有产品解决了多少)"放入四象限。',
  246. color: BRAND.PURPLE,
  247. })}
  248. <!-- 四象限 -->
  249. <div style="margin-top:24px;">
  250. ${renderMatrix2x2({
  251. xLabel: '满足度', yLabel: '声量',
  252. xLow: '低满足', xHigh: '高满足',
  253. yLow: '低声量', yHigh: '高声量',
  254. quadrants,
  255. accent: BRAND.PURPLE,
  256. })}
  257. </div>
  258. <!-- 5大共性需求 -->
  259. <div style="margin-top:20px; padding:16px 20px; background:var(--bg-1); border:1px solid var(--border); border-radius:var(--radius-md);">
  260. <div class="text-mono text-xs" style="color:${BRAND.CORAL}; letter-spacing:0.12em; margin-bottom:12px; font-weight:700;">5 大跨层共性未满足需求</div>
  261. <div style="display:grid; grid-template-columns:repeat(5, 1fr); gap:10px;">
  262. ${[
  263. { num: '01', need: '安全减量', desc: '想减药但怕反弹' },
  264. { num: '02', need: '好喝养护', desc: '不苦/不冲泡/随时可喝' },
  265. { num: '03', need: '可感知效果', desc: '喝完要有"暖暖的"体感' },
  266. { num: '04', need: '无疗程压力', desc: '想喝就喝/不需坚持三个月' },
  267. { num: '05', need: 'OTC级信任', desc: '比食品可信/比药品温和' },
  268. ].map(n => `
  269. <div style="padding:12px 10px; background:${BRAND.JIANG_GREEN}06; border:1px solid ${BRAND.JIANG_GREEN}30; border-radius:var(--radius-sm); text-align:center;">
  270. <div style="font-family:var(--font-head); font-size:1.2rem; font-weight:900; color:${BRAND.JIANG_GREEN};">${n.num}</div>
  271. <div style="font-weight:700; font-size:0.82rem; color:var(--text-1); margin-top:4px;">${esc(n.need)}</div>
  272. <div style="font-size:0.7rem; color:var(--text-3); margin-top:4px;">${esc(n.desc)}</div>
  273. </div>`).join('')}
  274. </div>
  275. </div>
  276. <div style="margin-top:14px; padding:12px 20px; background:${BRAND.JIANG_GREEN}08; border-left:4px solid ${BRAND.JIANG_GREEN}; border-radius:0 var(--radius-sm) var(--radius-sm) 0;">
  277. <div style="font-size:0.85rem; color:var(--text-1);">
  278. 下一章将对每个需求逐一验证:猴菇饮的产品力能不能真正接住?哪些是能力范围内的,哪些是需要话术/定位来解决的?
  279. </div>
  280. </div>
  281. </div>
  282. </section>`;
  283. }
  284. module.exports = { renderSub1, renderSub2, renderSub3, renderSub4 };