#!/usr/bin/env node const fs = require('fs'); const os = require('os'); const path = require('path'); const { spawnSync } = require('child_process'); const { buildRecords, readCsv } = require('./history-dataset-from-csv'); const { auditRecords, loadRecords } = require('./historical-dataset-audit'); function main() { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-history-from-csv-')); const input = path.join(dir, 'history.csv'); const output = path.join(dir, 'history-dataset'); fs.writeFileSync(input, withBom([ 'brief编号,项目名称,类目,客户原始Brief,历史人工补号量基线,参考账号或视频,平台,博主名称,主页链接,人工复核标签,客户选择,拒绝原因', 'dha-2026,DHA历史Brief,母婴,母婴DHA真实历史Brief,含预算和参考账号,6,https://www.xiaohongshu.com/user/profile/ref-dha,小红书,营养师妈妈,https://example.com/dha/a,可直接发客户,客户选中,', 'dha-2026,DHA历史Brief,母婴,母婴DHA真实历史Brief,含预算和参考账号,6,https://www.xiaohongshu.com/user/profile/ref-dha,小红书,泛生活号,https://example.com/dha/b,跑偏,客户拒绝,内容方向偏离DHA', 'skin-2026,修护历史Brief,护肤,敏感肌修护真实历史Brief,含禁投项,4,https://www.xiaohongshu.com/explore/ref-skin,小红书,敏感肌成分党,https://example.com/skin/a,商务复核,客户选中,', 'skin-2026,修护历史Brief,护肤,敏感肌修护真实历史Brief,含禁投项,4,https://www.xiaohongshu.com/explore/ref-skin,小红书,硬广导购号,https://example.com/skin/b,调性不符,客户拒绝,硬广感太强', 'home-2026,家清历史Brief,家清,家清清洁真实历史Brief,含人群和场景,5,https://www.douyin.com/user/ref-home,抖音,家居清洁实验室,https://example.com/home/a,可直接发客户,客户选中,', 'home-2026,家清历史Brief,家清,家清清洁真实历史Brief,含人群和场景,5,https://www.douyin.com/user/ref-home,抖音,装修设计号,https://example.com/home/b,跑偏,客户拒绝,内容重装修设计' ].join('\n')), 'utf8'); const records = buildRecords(readCsv(input)); assert(records.length === 3, 'should group rows into three history briefs'); assert(records[0].manualFinalList.length === 2, 'should keep two manual list rows per brief'); assert(records[0].customerFeedback.length === 2, 'should keep customer feedback rows'); assert(records[0].referenceLinks.length === 1, 'should dedupe reference links per brief'); const result = spawnSync(process.execPath, [ path.join(__dirname, 'history-dataset-from-csv.js'), '--input', input, '--output', output ], { cwd: path.resolve(__dirname, '..'), encoding: 'utf8' }); if (result.status !== 0) { process.stderr.write(result.stderr || result.stdout || ''); throw new Error('history-dataset-from-csv failed'); } assert(fs.existsSync(path.join(output, 'dha-2026.json')), 'should write per-brief json file'); const summary = auditRecords({ inputPath: output, records: loadRecords(output), minBriefs: 3 }); assert(summary.acceptance.readyForLongRun, 'converted dataset should be ready for long run'); assert(summary.acceptance.readyForCustomerEffectProof, 'converted dataset should be ready for customer effect proof'); assert(summary.withManualSupplementBaseline === 3, 'converted dataset should include manual supplement baselines'); const missingInput = path.join(dir, 'history-missing.csv'); fs.writeFileSync(missingInput, withBom([ 'brief编号,项目名称,类目,客户原始Brief,参考账号或视频,平台,博主名称,主页链接,人工复核标签,客户选择,拒绝原因', 'missing-2026,缺基线Brief,护肤,缺人工补号量基线,https://example.com/ref,小红书,敏感肌记录者,https://example.com/missing/a,可直接发客户,客户选中,' ].join('\n')), 'utf8'); const missingRecords = buildRecords(readCsv(missingInput)); const missingSummary = auditRecords({ inputPath: missingInput, records: missingRecords.map((record, index) => ({ file: `csv-row-${index}.json`, record })), minBriefs: 1 }); assert(missingSummary.acceptance.readyForLongRun, 'missing baseline should still allow basic long-run readiness'); assert(!missingSummary.acceptance.readyForCustomerEffectProof, 'missing baseline should block customer effect proof'); console.log(JSON.stringify({ ok: true, input, output, briefCount: records.length, readyForCustomerEffectProof: summary.acceptance.readyForCustomerEffectProof }, null, 2)); } function withBom(text) { return `\uFEFF${text}`; } function assert(condition, message) { if (!condition) throw new Error(message); } if (require.main === module) main();