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