case-template-import-smoke.js 4.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. #!/usr/bin/env node
  2. const {
  3. parseCasesFromSheets,
  4. buildFewshotCases,
  5. buildInsights,
  6. buildHistoryCsv,
  7. buildRuleSuggestions,
  8. extractSignalTags
  9. } = require('./case-template-import');
  10. // 模拟 readXlsxRows 返回的 sheets 结构(含标题行、说明行、表头行、数据行)
  11. const SHEETS = [
  12. {
  13. name: '案例喂养',
  14. rows: [
  15. ['AI 选号 · 案例喂养模板(每单执行后回填,用于训练选号模型)'],
  16. ['说明:结果标签用于 few-shot/反向学习;关键原因请写实,越具体模型越准。'],
  17. ['案例ID', '品类', '投放目标', '目标人群', '品牌调性', '平台', '选中账号特征', '结果标签', '关键原因', '客户反馈', '执行数据'],
  18. ['如 CASE-2026-001', '美妆护肤/3C/母婴...', '种草转化/品牌曝光...', '如 20-28岁油痘肌女性', '硬核测评/生活化种草...', 'douyin/xiaohongshu...', '粉丝量/互动率/调性/报价 摘要', '爆文 / 达标 / 一般 / 翻车', '为什么成/败?', '确认/修改/否决及理由', '互动量/ROI/搜索提升等'],
  19. ['CASE-2026-001', '美妆护肤', '种草转化', '20-28岁油痘肌女性', '硬核测评', 'xiaohongshu', '18w粉/互动率3.5%/成分党/报价8k', '爆文', '赞藏比高+垂直度92%,与油皮人群高度契合', '客户确认续投', '单篇赞评藏1200,搜索占位TOP3'],
  20. ['CASE-2026-002', '美妆护肤', '种草转化', '20-28岁油痘肌女性', '硬核测评', 'douyin', '500w粉/互动率0.8%/搞笑/报价20w', '翻车', '粉丝画像匹配仅35%,互动率虚胖,内容疲劳', '客户否决', '无转化,评论区负面'],
  21. ['CASE-2026-003', '3C', '品牌曝光', '25-35岁男性', '硬核测评', 'douyin', '200w粉/互动率1.2%/科技测评/报价15w', '一般', '数据达标但人群略泛', '待二次确认', '阅读稳定']
  22. ]
  23. }
  24. ];
  25. function main() {
  26. const cases = parseCasesFromSheets(SHEETS);
  27. assert(cases.length === 3, '应解析出 3 条案例,跳过说明行');
  28. const positive = cases.filter(item => item.outcome === 'positive');
  29. const neutral = cases.filter(item => item.outcome === 'neutral');
  30. const negative = cases.filter(item => item.outcome === 'negative');
  31. assert(positive.length === 1, '爆文应为正样本');
  32. assert(neutral.length === 1, '一般应为中性样本');
  33. assert(negative.length === 1, '翻车应为负样本');
  34. assert(cases[0].platform === 'xiaohongshu', '平台应归一化为内部代码');
  35. assert(cases[0].platformLabel === '小红书', '平台中文标签应正确');
  36. assert(cases[1].platform === 'douyin', '抖音平台应归一化');
  37. assert(extractSignalTags('互动率虚胖,粉丝画像匹配仅35%').includes('数据虚胖'), '应识别数据虚胖信号');
  38. assert(extractSignalTags('垂直度92%,人群高度契合').includes('人群契合'), '应识别人群契合信号');
  39. const fewshot = buildFewshotCases(cases, 'case_template.xlsx');
  40. assert(fewshot.version === 1, 'few-shot 库应带版本号');
  41. assert(fewshot.cases.length === 3, 'few-shot 库应包含全部案例');
  42. assert(fewshot.cases[0].outcome === 'positive', 'few-shot 库应保留 outcome 方向');
  43. const insights = buildInsights(cases);
  44. assert(insights.includes('正样本共性'), '洞察应包含正样本段落');
  45. assert(insights.includes('负样本共性'), '洞察应包含负样本段落');
  46. assert(insights.includes('CASE-2026-002'), '洞察应包含翻车案例');
  47. const historyCsv = buildHistoryCsv(cases);
  48. assert(historyCsv.startsWith('brief编号'), 'history CSV 应以 HISTORY_HEADER 开头');
  49. assert(historyCsv.includes('CASE-2026-002') && historyCsv.includes('客户拒绝'), '负样本应映射为客户拒绝');
  50. assert(historyCsv.includes('粉丝画像匹配仅35%'), '负样本拒绝原因应写入 CSV');
  51. // 规则建议:两条负样本同属「互动率」信号才会触发建议,当前只有一条负样本,应返回空
  52. assert(buildRuleSuggestions(cases).length === 0, '样本不足时不应生成高频规则建议');
  53. console.log(JSON.stringify({
  54. ok: true,
  55. caseCount: cases.length,
  56. positive: positive.length,
  57. neutral: neutral.length,
  58. negative: negative.length,
  59. fewshotCases: fewshot.cases.length
  60. }, null, 2));
  61. }
  62. function assert(condition, message) {
  63. if (!condition) throw new Error(message);
  64. }
  65. if (require.main === module) main();