Files
ObsidianHome/小鱼/.cdp/items-analyze2.mjs
T
2026-11-05 15:16:24 +08:00

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');