109 lines
6.4 KiB
JavaScript
109 lines
6.4 KiB
JavaScript
// 复核指导文档中的关键数字(全部从原始 JSON 重算)
|
|
import fs from 'node:fs';
|
|
const J = JSON.parse(fs.readFileSync('.cdp/out/items-7d-30d.json', 'utf8'));
|
|
const N = s => Number(s) || 0;
|
|
const d7 = J.data.recent7d.items, d30 = J.data.recent30d.items;
|
|
|
|
const kw = (items, re) => items.filter(x => re.test(String(x.name)));
|
|
const agg = g => ({
|
|
n: g.length,
|
|
amt: +g.reduce((a, b) => a + N(b.amt), 0).toFixed(2),
|
|
ord: g.reduce((a, b) => a + N(b.ord), 0),
|
|
pv: g.reduce((a, b) => a + N(b.pv), 0),
|
|
});
|
|
const eff = a => a.pv ? +(a.ord / a.pv * 1000).toFixed(2) : 0;
|
|
const sum = (items, f) => items.reduce((a, b) => a + N(b[f]), 0);
|
|
const catAgg = (items, c4) => agg(items.filter(x => x.c4 === c4));
|
|
|
|
const R = [];
|
|
const chk = (label, got, want) => {
|
|
const ok = String(got) === String(want);
|
|
R.push(`${ok ? 'PASS' : '**FAIL**'} | ${label} | 实算 ${got} | 文档 ${want}`);
|
|
};
|
|
const chkNear = (label, got, want, tol = 0.02) => {
|
|
const ok = Math.abs(Number(got) - Number(want)) <= tol;
|
|
R.push(`${ok ? 'PASS' : '**FAIL**'} | ${label} | 实算 ${got} | 文档 ${want}`);
|
|
};
|
|
|
|
chk('商品数 7天/30天', d7.length + '/' + d30.length, '350/350');
|
|
|
|
const dead30 = d30.filter(x => N(x.ord) === 0);
|
|
const dead7 = d7.filter(x => N(x.ord) === 0);
|
|
chk('30天零成交商品数', dead30.length, 220);
|
|
chk('30天零成交占比%', (dead30.length / d30.length * 100).toFixed(1), '62.9');
|
|
chk('30天零成交吃掉曝光', sum(dead30, 'pv'), 65841);
|
|
chk('30天零成交曝光占比%', (sum(dead30, 'pv') / sum(d30, 'pv') * 100).toFixed(1), '26.7');
|
|
chk('7天零成交商品数', dead7.length, 305);
|
|
chk('7天零成交占比%', (dead7.length / d7.length * 100).toFixed(1), '87.1');
|
|
chk('7天零成交吃掉曝光', sum(dead7, 'pv'), 27602);
|
|
chk('7天有成交商品数', d7.filter(x => N(x.ord) > 0).length, 45);
|
|
chk('30天有成交商品数', d30.filter(x => N(x.ord) > 0).length, 130);
|
|
|
|
chk('7天 ipv=0', d7.filter(x => N(x.ipv) === 0).length, 130);
|
|
chk('30天 ipv=0', d30.filter(x => N(x.ipv) === 0).length, 39);
|
|
|
|
const hi7 = d7.filter(x => N(x.pv) >= 400 && N(x.ord) === 0);
|
|
const hi30 = d30.filter(x => N(x.pv) >= 400 && N(x.ord) === 0);
|
|
chk('7天 曝光>=400 且 0 单:个数/曝光', hi7.length + '/' + sum(hi7, 'pv'), '10/6815');
|
|
chk('30天 曝光>=400 且 0 单:个数/曝光', hi30.length + '/' + sum(hi30, 'pv'), '47/39136');
|
|
const top5 = [...hi30].sort((a, b) => N(b.pv) - N(a.pv)).slice(0, 5);
|
|
chk('30天零成交曝光 Top5 合计', sum(top5, 'pv'), 8973);
|
|
chk('30天零成交曝光第一名 ID/曝光', top5[0].id + '/' + top5[0].pv, '1087247905718/2573');
|
|
chk('30天零成交曝光第二名 ID/曝光', top5[1].id + '/' + top5[1].pv, '1088948629635/1802');
|
|
|
|
const cats30 = new Set(d30.map(x => x.c4));
|
|
const deadOnly = [...cats30].filter(c => !d30.some(x => x.c4 === c && N(x.ord) > 0));
|
|
chk('30天整类目零成交:类目数', deadOnly.length, 60);
|
|
chk('30天整类目零成交:商品数', d30.filter(x => deadOnly.includes(x.c4)).length, 94);
|
|
|
|
const D5 = agg(kw(d30, /D5\b/i)), D5_7 = agg(kw(d7, /D5\b/i));
|
|
chk('30天 D5:商品/金额/单', D5.n + '/' + D5.amt + '/' + D5.ord, '3/46.8/6');
|
|
chk('30天 D5 效率‰', eff(D5), 6.20);
|
|
chk('7天 D5 效率‰', eff(D5_7), 9.35);
|
|
const Q3 = agg(kw(d30, /Q3\b/i));
|
|
chk('30天 Q3:商品/金额/单', Q3.n + '/' + Q3.amt + '/' + Q3.ord, '3/130.8/16');
|
|
chk('30天 Q3 效率‰', eff(Q3), 7.27);
|
|
const KP = agg(kw(d30, /Keep/i));
|
|
chk('30天 Keep:商品/金额/单/效率', KP.n + '/' + KP.amt + '/' + KP.ord + '/' + eff(KP), '6/141.3/9/6.46');
|
|
const BR = agg(kw(d30, /底托|支架|底座/i));
|
|
chk('30天 底托/支架:商品/金额/单', BR.n + '/' + BR.amt + '/' + BR.ord, '9/420/13');
|
|
const CHG = agg(kw(d30, /充电[线器]|数据线/i)), CHG7 = agg(kw(d7, /充电[线器]|数据线/i));
|
|
chk('30天 充电线/器:商品/金额/单/效率', CHG.n + '/' + CHG.amt + '/' + CHG.ord + '/' + eff(CHG), '20/163.3/14/1.73');
|
|
chk('7天 充电线/器 订单数', CHG7.ord, 0);
|
|
const CB = agg(kw(d30, /组合|\+|套装/i));
|
|
chk('30天 组合装:商品/金额/单/效率', CB.n + '/' + CB.amt + '/' + CB.ord + '/' + eff(CB), '25/185.7/14/1.35');
|
|
const M10 = agg(kw(d30, /手环\s*10|手环10/i));
|
|
chk('30天 小米手环10:商品/金额/单/效率', M10.n + '/' + M10.amt + '/' + M10.ord + '/' + eff(M10), '7/48.5/5/0.86');
|
|
const TUO = catAgg(d30, '脱毛器');
|
|
chk('30天 脱毛器(类目):商品/金额/单/效率‰', TUO.n + '/' + TUO.amt + '/' + TUO.ord + '/' + eff(TUO), '8/122.7/2/0.26');
|
|
const SCI = catAgg(d30, '科学实验');
|
|
chk('30天 科学实验(类目):商品/金额/单/曝光/效率‰', SCI.n + '/' + SCI.amt + '/' + SCI.ord + '/' + SCI.pv + '/' + eff(SCI), '12/166.1/16/18381/0.87');
|
|
chk('7天 科学实验(类目) 订单数', catAgg(d7, '科学实验').ord, 0);
|
|
|
|
const NEWM = d30.filter(x => /手环11|S1[01]|FIT\s*[45]/i.test(String(x.name)));
|
|
chk('30天 新型号表带:条数/曝光/询单/单', NEWM.length + '/' + sum(NEWM, 'pv') + '/' + sum(NEWM, 'chat') + '/' + sum(NEWM, 'ord'), '4/4229/2/0');
|
|
|
|
const byId = id => d30.find(x => x.id === id);
|
|
const z8 = byId('1072025279094');
|
|
chkNear('Z8底托 客单价', (N(z8.amt) / N(z8.ord)).toFixed(2), 31.32);
|
|
const d2a = byId('953520780202');
|
|
chkNear('D2A表带 客单价', (N(d2a.amt) / N(d2a.ord)).toFixed(2), 7.55);
|
|
chkNear('客单倍数 Z8/D2A', (N(z8.amt) / N(z8.ord) / (N(d2a.amt) / N(d2a.ord))).toFixed(1), 4.1, 0.05);
|
|
const q3 = byId('1083300318205');
|
|
chk('Q3主链接 30天 单/金额/cvr', q3.ord + '/' + q3.amt + '/' + q3.cvr, '16/130.80/50.00%');
|
|
const q3_7 = d7.find(x => x.id === '1083300318205');
|
|
chk('Q3主链接 7天 单/曝光/浏览/询单', q3_7.ord + '/' + q3_7.pv + '/' + q3_7.ipvUv + '/' + q3_7.chat, '0/420/5/0');
|
|
const z6_7 = d7.find(x => x.id === '1060307067942');
|
|
chk('Z6Pro 7天 单/曝光/浏览/询单', z6_7.ord + '/' + z6_7.pv + '/' + z6_7.ipvUv + '/' + z6_7.chat, '0/435/10/0');
|
|
const d5 = byId('1083298794612');
|
|
chk('D5主链接 30天 单/金额/cvr', d5.ord + '/' + d5.amt + '/' + d5.cvr, '6/46.80/35.29%');
|
|
|
|
chk('7天 明细 单/金额', sum(d7, 'ord') + '/' + sum(d7, 'amt').toFixed(2), '97/1017.70');
|
|
chk('30天 明细 单/金额', sum(d30, 'ord') + '/' + sum(d30, 'amt').toFixed(2), '560/5690.00');
|
|
chkNear('7天 金额缺口', (1106.70 - sum(d7, 'amt')).toFixed(2), 89.00);
|
|
chkNear('30天 金额缺口', (5870.30 - sum(d30, 'amt')).toFixed(2), 180.30);
|
|
|
|
const fails = R.filter(r => r.includes('FAIL')).length;
|
|
R.unshift(`复核结果:${R.length - 1} 项,FAIL ${fails} 项\n`);
|
|
fs.writeFileSync('.cdp/out/verify-guidance.md', R.join('\n'), 'utf8');
|