90 lines
5.2 KiB
JavaScript
90 lines
5.2 KiB
JavaScript
// 单品级深度分析 v2
|
|
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);
|
|
|
|
const MODELS = [
|
|
['小天才 D2A/Q2A', /D2A|Q2A|D2\b|Q1A|D2\b/i],
|
|
['小天才 D3 系', /D3S|D3\b|W2028/i],
|
|
['小天才 D5', /D5\b/i],
|
|
['小天才 Q3', /Q3\b/i],
|
|
['小天才 Z6Pro', /Z6Pro|Z6\b/i],
|
|
['小天才 Z7/Z7S', /Z7/i],
|
|
['小天才 Z8', /Z8/i],
|
|
['小天才 Z9/Z9少年', /Z9/i],
|
|
['小天才 Z10/Z11', /Z1[01]/i],
|
|
['小天才 X10/保护壳', /X10|保护[壳套]/i],
|
|
['小天才 Y6/其他', /Y6|Z2A|Z5|Z1\b/i],
|
|
['小米手环 9Pro/8Pro', /手环\s*9Pro|手环\s*8Pro|9Pro|8Pro/i],
|
|
['小米手环 10', /手环\s*10|手环10/i],
|
|
['小米手环 3/其他', /手环\s*3|手环\s*4|手环\s*5|手环\s*6|手环\s*7/i],
|
|
['Redmi Watch 3/5/6', /watch\s*[356]|watch[356]/i],
|
|
['华为 儿童 4Pro/5', /4Pro|华为.*5|活力版/i],
|
|
['华为 Band 8/9', /Band\s*[89]|ASK-B19|B29/i],
|
|
['Keep 手环', /Keep/i],
|
|
['防丢挂绳', /挂绳|防丢/i],
|
|
['充电线/充电器', /充电[线器]|数据线/i],
|
|
['卡盖/卡托/螺丝杆', /螺丝杆|卡盖|卡托|拆装/i],
|
|
['底托/支架', /底托|支架|底座/i],
|
|
['组合装', /组合|\+|套装/i],
|
|
['表带(未识别型号)', /表带|腕带/i],
|
|
];
|
|
|
|
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 sA = items.reduce((a, b) => a + N(b.amt), 0);
|
|
const sO = items.reduce((a, b) => a + N(b.ord), 0);
|
|
const sP = items.reduce((a, b) => a + N(b.pv), 0);
|
|
const sI = items.reduce((a, b) => a + N(b.ipv), 0);
|
|
say(`\n${'='.repeat(104)}`);
|
|
say(`## ${label} 商品数=${items.length} 订单=${sO} 金额=¥${sA.toFixed(2)} 曝光=${sP} 浏览=${sI}`);
|
|
say(` 对账缺口:总览订单 102/584、金额 1106.70/5870.30 → 缺口 ${dt === 'recent7d' ? '5单/¥89.00' : '待算'}`);
|
|
|
|
say(`\n### ${label} · 零浏览商品(ipv=0,即"被浏览商品数"口径)`);
|
|
const zeroI = items.filter(x => N(x.ipv) === 0);
|
|
say(` 共 ${zeroI.length} 个 / ${items.length}(占 ${(zeroI.length / items.length * 100).toFixed(1)}%),合计曝光 ${zeroI.reduce((a, b) => a + N(b.pv), 0)}`);
|
|
|
|
say(`\n### ${label} · 型号关键词聚合(按成交金额)`);
|
|
say(' 型号/方向 | 商品数 | 金额 | 单 | 曝光 | 询单 | 有单商品数 | 单/曝光‰');
|
|
const seen = new Set();
|
|
const rows = [];
|
|
for (const [name, re] of MODELS) {
|
|
const g = items.filter(x => re.test(String(x.name)));
|
|
if (!g.length) continue;
|
|
rows.push({
|
|
name,
|
|
n: g.length,
|
|
amt: g.reduce((a, b) => a + N(b.amt), 0),
|
|
ord: g.reduce((a, b) => a + N(b.ord), 0),
|
|
pv: g.reduce((a, b) => a + N(b.pv), 0),
|
|
chat: g.reduce((a, b) => a + N(b.chat), 0),
|
|
win: g.filter(x => N(x.ord) > 0).length,
|
|
});
|
|
}
|
|
rows.sort((a, b) => b.amt - a.amt);
|
|
for (const r of rows) say(` ${r.name.padEnd(18)} | ${String(r.n).padStart(3)} | ¥${r.amt.toFixed(2).padStart(8)} | ${String(r.ord).padStart(3)} | ${String(r.pv).padStart(6)} | ${String(r.chat).padStart(3)} | ${String(r.win).padStart(2)}/${String(r.n).padStart(3)} | ${r.pv ? (r.ord / r.pv * 1000).toFixed(2) : '-'}`);
|
|
|
|
if (dt === 'recent30d') {
|
|
say(`\n### ${label} · 支付金额 Top30`);
|
|
say(' # | 商品ID | 金额 | 单 | 曝光 | 浏览人 | 询单 | 转化率 | 价 | 天 | 类目 | 名称');
|
|
[...items].sort((a, b) => N(b.amt) - N(a.amt)).slice(0, 30).forEach((x, i) => say(
|
|
` ${String(i + 1).padStart(2)} | ${x.id} | ¥${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(7)} | ¥${x.price.padStart(5)} | ${String(x.days).padStart(3)} | ${String(x.c4).padEnd(7)} | ${String(x.name).slice(0, 26)}`));
|
|
say(`\n### ${label} · 高曝光零成交(曝光>=500 且 0 单)`);
|
|
const w = items.filter(x => N(x.pv) >= 500 && N(x.ord) === 0).sort((a, b) => N(b.pv) - N(a.pv));
|
|
w.forEach(x => say(` ${x.id} 曝光${String(x.pv).padStart(5)} 浏览人${String(x.ipvUv).padStart(4)} 询单${String(x.chat).padStart(2)} ¥${x.price.padStart(6)} ${String(x.c4).padEnd(7)} | ${String(x.name).slice(0, 34)}`));
|
|
say(` 合计 ${w.length} 个,吃掉曝光 ${w.reduce((a, b) => a + N(b.pv), 0)}`);
|
|
say(`\n### ${label} · 类目聚合`);
|
|
const byC = {};
|
|
for (const x of items) { const k = x.c4 || '未知'; byC[k] = byC[k] || { n: 0, amt: 0, ord: 0, pv: 0, win: 0 }; byC[k].n++; byC[k].amt += N(x.amt); byC[k].ord += N(x.ord); byC[k].pv += N(x.pv); if (N(x.ord) > 0) byC[k].win++; }
|
|
say(' 类目 | 商品数 | 金额 | 单 | 曝光 | 有单商品 | 单/曝光‰');
|
|
Object.entries(byC).sort((a, b) => b[1].amt - a[1].amt).forEach(([k, v]) => say(
|
|
` ${k.padEnd(12)} | ${String(v.n).padStart(3)} | ¥${v.amt.toFixed(2).padStart(8)} | ${String(v.ord).padStart(3)} | ${String(v.pv).padStart(6)} | ${String(v.win).padStart(2)}/${String(v.n).padStart(3)} | ${v.pv ? (v.ord / v.pv * 1000).toFixed(2) : '-'}`));
|
|
}
|
|
}
|
|
|
|
fs.writeFileSync('.cdp/out/items-analysis2.md', L.join('\n'), 'utf8');
|