gen-voc-report.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. #!/usr/bin/env node
  2. /**
  3. * 江中 VOC 报告生成主入口
  4. *
  5. * 输入:data/jiangzhong-{product}-voc.json
  6. * 输出:reports/jiangzhong-{product}-voc-report.html
  7. *
  8. * 用法:
  9. * node scripts/tools/gen-voc-report.js # 全量 3 产品
  10. * node scripts/tools/gen-voc-report.js liver # 指定单产品
  11. */
  12. const fs = require('fs');
  13. const path = require('path');
  14. const { analyze, extractThemes, cleanText } = require('./voc-analyze');
  15. const { buildHTML } = require('./voc-render');
  16. const PRODUCTS = [
  17. { id: 'liver', file: 'jiangzhong-liver-voc.json' },
  18. { id: 'probiotic', file: 'jiangzhong-probiotic-voc.json' },
  19. { id: 'monkey', file: 'jiangzhong-monkey-voc.json' },
  20. ];
  21. const DATA_DIR = path.resolve(__dirname, '..', '..', 'data');
  22. const REPORT_DIR = path.resolve(__dirname, '..', '..', 'reports');
  23. if (!fs.existsSync(REPORT_DIR)) fs.mkdirSync(REPORT_DIR, { recursive: true });
  24. function generateOne(productId) {
  25. const target = PRODUCTS.find((p) => p.id === productId);
  26. if (!target) throw new Error(`unknown product: ${productId}`);
  27. const vocPath = path.join(DATA_DIR, target.file);
  28. if (!fs.existsSync(vocPath)) {
  29. console.log(` ⚠️ ${target.id}: 数据缺失 ${vocPath}`);
  30. return;
  31. }
  32. const voc = JSON.parse(fs.readFileSync(vocPath, 'utf-8'));
  33. const A = analyze(voc);
  34. // 收集语料做主题聚类
  35. const commentTexts = A.topComments.map((c) => cleanText(c.content || ''));
  36. const noteTexts = A.topNotes.map((n) => cleanText((n.title || '') + ' ' + (n.desc || '')));
  37. const themes = extractThemes([...commentTexts, ...noteTexts]);
  38. const html = buildHTML({
  39. product: voc.product,
  40. category: voc.category,
  41. collectedAt: new Date(voc.collected_at).toLocaleDateString('zh-CN'),
  42. A,
  43. themes,
  44. });
  45. const outPath = path.join(REPORT_DIR, `jiangzhong-${target.id}-voc-report.html`);
  46. fs.writeFileSync(outPath, html, 'utf-8');
  47. const kb = (html.length / 1024).toFixed(1);
  48. console.log(` ✅ ${voc.product}: ${outPath} (${kb}KB, ${A.metrics.notes} 笔记 / ${A.metrics.sampleComments} 评论)`);
  49. }
  50. function main() {
  51. const argId = process.argv[2];
  52. console.log('');
  53. console.log('╔══════════════════════════════════════════════════════════╗');
  54. console.log('║ 江中 VOC 报告生成器 ║');
  55. console.log('╚══════════════════════════════════════════════════════════╝');
  56. console.log(` 输出目录: ${REPORT_DIR}`);
  57. console.log('');
  58. if (argId) {
  59. generateOne(argId);
  60. } else {
  61. for (const p of PRODUCTS) {
  62. try { generateOne(p.id); }
  63. catch (e) { console.log(` ❌ ${p.id}: ${e.message}`); }
  64. }
  65. }
  66. console.log('');
  67. console.log('📄 Done.');
  68. }
  69. main();