industry-trend-profile-builder.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const { parseList } = require('./industry-trend-report');
  5. const DEFAULT_RESULT_PREFIX = 'INDUSTRY_TREND_PROFILE_RESULT';
  6. function parseArgs(argv) {
  7. const args = {};
  8. for (let i = 0; i < argv.length; i++) {
  9. const token = argv[i];
  10. if (!token.startsWith('--')) continue;
  11. const eq = token.indexOf('=');
  12. if (eq >= 0) {
  13. args[token.slice(2, eq)] = token.slice(eq + 1);
  14. } else {
  15. const key = token.slice(2);
  16. const next = argv[i + 1];
  17. if (next && !next.startsWith('--')) {
  18. args[key] = next;
  19. i++;
  20. } else {
  21. args[key] = true;
  22. }
  23. }
  24. }
  25. return args;
  26. }
  27. function ensureDir(dirPath) {
  28. fs.mkdirSync(dirPath, { recursive: true });
  29. }
  30. function inferFromMessage(message) {
  31. const text = String(message || '');
  32. const inferred = {};
  33. if (/家装|全屋定制|装修|衣柜|橱柜/.test(text)) {
  34. inferred.project = 'home-design-trend';
  35. inferred.industry = '家装全屋定制';
  36. inferred.businessType = '门店/设计服务/全屋定制服务商';
  37. inferred.targetAudience = ['女性客户', '家庭装修决策者', '门店到访客户'];
  38. inferred.decisionMakers = ['女主人', '夫妻共同决策者', '设计师影响者'];
  39. inferred.productsOrServices = ['全屋定制', '衣柜设计', '橱柜设计', '玄关柜', '小户型收纳'];
  40. inferred.trendQuestions = [
  41. '当季哪些装修风格正在升温?',
  42. '女性客户最关心哪些设计元素和翻车风险?',
  43. '下季度哪些风格、颜色、材质值得门店提前准备案例?',
  44. '哪些小红书内容角度适合设计师和门店获客?'
  45. ];
  46. inferred.keywords = ['全屋定制', '奶油风装修', '小户型收纳', '衣柜设计避坑', '厨房橱柜设计', '装修翻车'];
  47. inferred.mustTrackSignals = ['装修风格', '设计元素', '颜色材质', '收纳需求', '环保顾虑', '预算焦虑', '翻车风险'];
  48. inferred.outputPreference = {
  49. reportTone: '老板视角',
  50. actionType: '趋势判断、门店转化、设计师沟通话术'
  51. };
  52. }
  53. return inferred;
  54. }
  55. function mergeProfile(base, args, inferred) {
  56. const profile = {
  57. ...base,
  58. project: args.project || inferred.project || 'industry-trend-project',
  59. industry: args.industry || inferred.industry || '未填写行业',
  60. businessType: args.businessType || args['business-type'] || inferred.businessType || '',
  61. targetAudience: parseList(args.targetAudience || args['target-audience']).length
  62. ? parseList(args.targetAudience || args['target-audience'])
  63. : (inferred.targetAudience || []),
  64. decisionMakers: parseList(args.decisionMakers || args['decision-makers']).length
  65. ? parseList(args.decisionMakers || args['decision-makers'])
  66. : (inferred.decisionMakers || []),
  67. productsOrServices: parseList(args.productsOrServices || args['products-or-services']).length
  68. ? parseList(args.productsOrServices || args['products-or-services'])
  69. : (inferred.productsOrServices || []),
  70. priceBand: args.priceBand || args['price-band'] || inferred.priceBand || '',
  71. regions: parseList(args.regions || inferred.regions),
  72. trendQuestions: parseList(args.trendQuestions || args['trend-questions']).length
  73. ? parseList(args.trendQuestions || args['trend-questions'])
  74. : (inferred.trendQuestions || []),
  75. platforms: parseList(args.platforms || inferred.platforms || 'xiaohongshu'),
  76. keywords: parseList(args.keywords).length ? parseList(args.keywords) : (inferred.keywords || []),
  77. competitors: parseList(args.competitors || inferred.competitors),
  78. mustTrackSignals: parseList(args.mustTrackSignals || args['must-track-signals']).length
  79. ? parseList(args.mustTrackSignals || args['must-track-signals'])
  80. : (inferred.mustTrackSignals || []),
  81. blockedDirections: parseList(args.blockedDirections || args['blocked-directions'] || inferred.blockedDirections),
  82. outputPreference: {
  83. reportTone: args.reportTone || args['report-tone'] || inferred.outputPreference?.reportTone || '老板视角',
  84. actionType: args.actionType || args['action-type'] || inferred.outputPreference?.actionType || '趋势判断、内容建议、转化动作'
  85. },
  86. updatedAt: new Date().toISOString()
  87. };
  88. return profile;
  89. }
  90. function main() {
  91. const args = parseArgs(process.argv.slice(2));
  92. const output = path.resolve(args.output || 'memory/industry-trend-profile.json');
  93. const base = args.base && fs.existsSync(path.resolve(args.base))
  94. ? JSON.parse(fs.readFileSync(path.resolve(args.base), 'utf8'))
  95. : {};
  96. const inferred = inferFromMessage(args.message);
  97. const profile = mergeProfile(base, args, inferred);
  98. ensureDir(path.dirname(output));
  99. fs.writeFileSync(output, JSON.stringify(profile, null, 2), 'utf8');
  100. const needsMore = !profile.industry || profile.industry === '未填写行业' || !profile.keywords.length;
  101. const assistantMessage = needsMore
  102. ? [
  103. '我先建了一个行业档案草稿,但还缺关键信息。',
  104. '',
  105. '请补充 3 点:',
  106. '1. 你要追踪的行业/品类是什么?',
  107. '2. 主要决策人和影响决策的人是谁?',
  108. '3. 最想知道哪些趋势问题?'
  109. ].join('\n')
  110. : [
  111. `行业档案已建立:${profile.industry}`,
  112. '',
  113. `已记录关键词:${profile.keywords.slice(0, 8).join('、') || '待补充'}`,
  114. `重点信号:${profile.mustTrackSignals.slice(0, 8).join('、') || '待补充'}`,
  115. '',
  116. '下一步可以直接启动行业趋势情报官生成日报。'
  117. ].join('\n');
  118. const prefix = args['result-prefix'] || args.resultPrefix || DEFAULT_RESULT_PREFIX;
  119. console.log(`${prefix}=${JSON.stringify({
  120. status: 'ok',
  121. profilePath: output,
  122. profile,
  123. needsMore,
  124. assistantMessage
  125. })}`);
  126. }
  127. if (require.main === module) {
  128. try {
  129. main();
  130. } catch (error) {
  131. console.error(error && error.stack ? error.stack : String(error));
  132. process.exit(1);
  133. }
  134. }