54 lines
3.4 KiB
JavaScript
54 lines
3.4 KiB
JavaScript
// 单品级分析
|
||
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 L = [];
|
||
const say = s => { L.push(s); console.log(s); };
|
||
|
||
for (const dt of ['recent7d', 'recent30d']) {
|
||
const d = J.data[dt];
|
||
if (!d) continue;
|
||
const items = d.items;
|
||
const label = dt === 'recent7d' ? '近7天' : '近30天';
|
||
const sumAmt = items.reduce((a, b) => a + N(b.amt), 0);
|
||
const sumOrd = items.reduce((a, b) => a + N(b.ord), 0);
|
||
const sumPv = items.reduce((a, b) => a + N(b.pv), 0);
|
||
say('\n' + '='.repeat(100));
|
||
say(`### ${label} 商品数=${items.length} 合计订单=${sumOrd} 合计金额=¥${sumAmt.toFixed(2)} 合计曝光=${sumPv}`);
|
||
say(` (对账:总览支付笔数 7天=102 / 30天=584;金额 7天=1106.70 / 30天=5870.30)`);
|
||
|
||
const top = [...items].sort((a, b) => N(b.amt) - N(a.amt)).slice(0, 25);
|
||
say(`\n--- ${label} 支付金额 Top25 ---`);
|
||
say('排名 | 商品ID | 金额 | 单 | 曝光 | 浏览人数 | 询单 | 转化率 | 价 | 在架天 | 名称');
|
||
top.forEach((x, i) => say(
|
||
`${String(i + 1).padStart(2)} | ${x.id} | ¥${String(N(x.amt).toFixed(2)).padStart(7)} | ${String(x.ord).padStart(2)} | ${String(x.pv).padStart(5)} | ${String(x.ipvUv).padStart(4)} | ${String(x.chat).padStart(2)} | ${String(x.cvr).padStart(6)} | ¥${x.price} | ${x.days} | ${String(x.name).slice(0, 30)}`));
|
||
|
||
const waste = items.filter(x => N(x.pv) >= 400 && N(x.ord) === 0).sort((a, b) => N(b.pv) - N(a.pv));
|
||
say(`\n--- ${label} 高曝光零成交(曝光>=400 且 0 单):${waste.length} 个 ---`);
|
||
waste.slice(0, 20).forEach(x => say(
|
||
` ${x.id} 曝光${String(x.pv).padStart(5)} 浏览人${String(x.ipvUv).padStart(4)} 询单${String(x.chat).padStart(2)} ¥${x.price} ${x.c4} | ${String(x.name).slice(0, 34)}`));
|
||
say(` 这 ${waste.length} 个商品合计吃掉曝光 ${waste.reduce((a, b) => a + N(b.pv), 0)}`);
|
||
|
||
const zero = items.filter(x => N(x.pv) === 0);
|
||
say(`\n--- ${label} 零曝光商品:${zero.length} 个(占 ${(zero.length / items.length * 100).toFixed(1)}%)---`);
|
||
|
||
const news = items.filter(x => N(x.days) <= 21).sort((a, b) => N(b.ord) - N(a.ord));
|
||
say(`\n--- ${label} 新品(在架<=21天):${news.length} 个,有成交 ${news.filter(x => N(x.ord) > 0).length} 个 ---`);
|
||
news.slice(0, 22).forEach(x => say(
|
||
` ${x.id} ${String(x.days).padStart(2)}天 曝光${String(x.pv).padStart(5)} 浏览人${String(x.ipvUv).padStart(4)} 询单${String(x.chat).padStart(2)} ${String(x.ord).padStart(2)}单 ¥${String(N(x.amt).toFixed(2)).padStart(7)} cvr${String(x.cvr).padStart(7)} ¥${x.price} ${x.c4} | ${String(x.name).slice(0, 30)}`));
|
||
|
||
const byC = {};
|
||
for (const x of items) {
|
||
const k = x.c4 || '未知';
|
||
byC[k] = byC[k] || { n: 0, amt: 0, ord: 0, pv: 0, chat: 0 };
|
||
byC[k].n++; byC[k].amt += N(x.amt); byC[k].ord += N(x.ord); byC[k].pv += N(x.pv); byC[k].chat += N(x.chat);
|
||
}
|
||
say(`\n--- ${label} 类目聚合 Top18(按金额)---`);
|
||
say('类目 | 商品数 | 金额 | 单 | 曝光 | 询单 | 单/曝光%');
|
||
Object.entries(byC).sort((a, b) => b[1].amt - a[1].amt).slice(0, 18).forEach(([k, v]) => say(
|
||
`${k} | ${v.n} | ¥${v.amt.toFixed(2)} | ${v.ord} | ${v.pv} | ${v.chat} | ${v.pv ? (v.ord / v.pv * 100).toFixed(3) : '-'}`));
|
||
}
|
||
|
||
fs.writeFileSync('.cdp/out/items-analysis.md', L.join('\n'), 'utf8');
|
||
console.log('\nwritten .cdp/out/items-analysis.md');
|