| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- #!/usr/bin/env node
- const fs = require('fs');
- const os = require('os');
- const path = require('path');
- const { buildMetrics, readCsv } = require('./review-metrics');
- function main() {
- const out = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-review-metrics-'));
- const csvPath = path.join(out, 'reviewed.csv');
- fs.writeFileSync(csvPath, withBom([
- 'brief编号,策略,排名,平台,博主名称,综合分,brief匹配分,参考风格分,主页证据分,视觉质感分,调性一致分,证据加分,证据风险扣分,推荐理由,风险提示,主页链接,人工复核标签,客户选择,归因类型',
- 'brief-a,result-first,1,小红书,敏感肌成分研究所,87,100,50,89,70,95,0,0,匹配敏感肌,需确认报价,http://example.com/a,可直接发客户,客户选中',
- 'brief-a,result-first,2,小红书,真实护肤记录,82,90,60,80,75,83,0,0,匹配真实体验,需确认档期,http://example.com/b,商务复核,',
- 'brief-a,result-first,3,小红书,泛生活方式号,62,50,40,30,45,41,0,0,泛生活方式,调性偏泛,http://example.com/c,跑偏,,需求解析错',
- 'brief-a,result-first,4,抖音,精致护肤测评,80,88,70,75,76,79,0,0,测评结构清楚,需补主页近作,http://example.com/d,商务复核,'
- ].join('\n')), 'utf8');
- const metrics = buildMetrics({ inputPath: csvPath, rows: readCsv(csvPath) });
- assert(metrics.headerOk, 'header should accept software table plus customer selection field');
- assert(metrics.duplicateKeyCount === 0, 'duplicate keys should be zero');
- assert(metrics.rankContinuous, 'ranks should be continuous');
- assert(metrics.businessUsableRate === 0.75, 'business usable rate should be 75%');
- assert(metrics.offTargetHardFailRate === 0.25, 'negative rate should be 25%');
- assert(metrics.customerSelectedRate === 0.25, 'customer selected rate should be 25%');
- assert(metrics.failureAttribution.failureRows === 1, 'one negative row should be counted');
- assert(metrics.failureAttribution.attributedCount === 1, 'negative row should have attribution');
- assert(metrics.acceptance.failureAttributionCoveragePass, 'attributed negative rows should pass attribution gate');
- assert(metrics.byStrategy.length === 1, 'strategy summary should include result-first');
- assert(metrics.strategyComparison.reference.total === 0, 'reference comparison should be pending without reference strategies');
- assert(metrics.acceptance.referencePassRateHigherThanKeyword === null, 'reference vs keyword gate should be pending without comparison groups');
- assert(!metrics.acceptance.overallPass, 'sample should fail because negative rate is above gate');
- const missingAttributionPath = path.join(out, 'missing-attribution.csv');
- fs.writeFileSync(missingAttributionPath, withBom([
- 'brief编号,策略,排名,平台,博主名称,综合分,brief匹配分,参考风格分,主页证据分,视觉质感分,调性一致分,证据加分,证据风险扣分,推荐理由,风险提示,主页链接,人工复核标签,归因类型',
- 'brief-b,result-first,1,小红书,不匹配账号,60,50,40,30,45,41,0,0,泛生活方式,调性偏泛,http://example.com/e,调性不符,'
- ].join('\n')), 'utf8');
- const missingMetrics = buildMetrics({ inputPath: missingAttributionPath, rows: readCsv(missingAttributionPath) });
- assert(missingMetrics.failureAttribution.failureRows === 1, 'tone mismatch should be treated as negative');
- assert(missingMetrics.failureAttribution.missingAttributionCount === 1, 'missing attribution should be counted');
- assert(!missingMetrics.acceptance.failureAttributionCoveragePass, 'missing attribution should fail attribution gate');
- const strategyComparePath = path.join(out, 'strategy-compare.csv');
- fs.writeFileSync(strategyComparePath, withBom([
- 'brief编号,策略,排名,平台,博主名称,综合分,brief匹配分,参考风格分,主页证据分,视觉质感分,调性一致分,证据加分,证据风险扣分,推荐理由,风险提示,主页链接,人工复核标签,客户选择,归因类型',
- 'brief-c,baseline-live,1,小红书,基础召回A,80,80,40,50,70,70,0,0,基础召回,需复核,http://example.com/f,商务复核,客户选中,',
- 'brief-c,baseline-live,2,小红书,基础召回B,78,78,40,50,70,70,0,0,基础召回,需复核,http://example.com/g,跑偏,,召回关键词错',
- 'brief-c,reference-account,3,小红书,参考召回A,90,90,85,80,80,85,2,0,参考账号相似,需复核,http://example.com/h,可直接发客户,客户选中,',
- 'brief-c,reference-account,4,小红书,参考召回B,88,88,82,78,80,85,2,0,参考账号相似,需复核,http://example.com/i,商务复核,客户选中,',
- 'brief-c,homepage-evidence,5,小红书,主页证据A,86,86,80,88,80,85,2,0,主页近作匹配,需复核,http://example.com/j,商务复核,,'
- ].join('\n')), 'utf8');
- const strategyMetrics = buildMetrics({ inputPath: strategyComparePath, rows: readCsv(strategyComparePath) });
- assert(strategyMetrics.strategyComparison.keyword.businessUsableRate === 0.5, 'keyword baseline usable rate should be 50%');
- assert(strategyMetrics.strategyComparison.reference.businessUsableRate === 1, 'reference usable rate should be 100%');
- assert(strategyMetrics.acceptance.referencePassRateHigherThanKeyword === true, 'reference strategies should outperform keyword baseline');
- assert(strategyMetrics.strategyComparison.reference.customerSelectedRate >= 0.4, 'reference customer selected rate should meet 40% gate');
- assert(strategyMetrics.acceptance.referenceCustomerSelectedRatePass === true, 'reference selected-rate gate should pass when measured');
- console.log(`review metrics smoke ok: ${out}`);
- }
- function withBom(text) {
- return `\uFEFF${text}`;
- }
- function assert(condition, message) {
- if (!condition) throw new Error(message);
- }
- if (require.main === module) main();
|