// 提取逐日序列 + 指标清单 + 分布,输出到文件 import fs from 'node:fs'; import path from 'node:path'; const dir = path.resolve(process.argv[2] || '.cdp/out/api'); const outFile = path.resolve(process.argv[3] || '.cdp/out/analysis-dump.md'); const files = fs.readdirSync(dir).filter(f => f.endsWith('.txt')).sort(); const load = (f) => { const raw = fs.readFileSync(path.join(dir, f), 'utf8'); const i = raw.indexOf('\n\n'); const url = raw.slice(5, i).trim(); let body = raw.slice(i + 2).trim(); let json = null; try { json = JSON.parse(body); } catch { const m = body.match(/^[^(]*\((.*)\)[;\s]*$/s); if (m) { try { json = JSON.parse(m[1]); } catch {} } } return { url, json, api: (url.match(/datacompass\.[A-Za-z0-9._]+/) || [''])[0] }; }; const deepFind = (o, key, hits = [], p = '') => { if (o === null || typeof o !== 'object') return hits; for (const k of Object.keys(o)) { const np = p ? p + '.' + k : k; if (k === key) hits.push({ path: np, value: o[k] }); deepFind(o[k], key, hits, np); } return hits; }; const L = []; const say = (s) => { L.push(s); }; for (const f of files) { const { json, api } = load(f); if (!json) continue; const rr = deepFind(json, 'realDateRange')[0]; const banner = deepFind(json, 'bannerDataList')[0]; const graph = deepFind(json, 'graphDataList')[0]; const cycle = banner?.value?.[0]?.cycle || ''; say('\n' + '='.repeat(90)); say(`FILE ${f}`); say(` api=${api.replace('datacompass.', '')} range=${JSON.stringify(rr?.value)} cycle=${cycle} bannerN=${banner?.value?.length ?? 0} graphN=${graph?.value?.length ?? 0}`); if (banner && f.startsWith('30d-') && Number(f.slice(4, 6)) >= 6) { say(' --- bannerDataList (指标总表) ---'); for (const b of banner.value) { say(` ${String(b.name).padEnd(22)} now=${String(b.dataStr).padEnd(12)} prev=${String(b.lastDataStr).padEnd(12)} Δ=${String(b.ratioFormat).padEnd(10)}`); } } if (graph && f.startsWith('30d-')) { const rows = graph.value; const keys = ['ds', 'payAmt', 'payOrdCnt', 'payByrCnt', 'aov', 'showPv', 'showUv', 'vstPv', 'vstUv', 'ipv', 'ipvUv', 'chatUv', 'favCnt', 'rep3minUvRate', 'rfdAmt', 'rfdOrdCnt', 'onlCnt', 'newItmCnt', 'stItmCnt', 'uctr', 'uctcvr', 'cmtItmCnt']; say(' --- graphDataList (逐日) ---'); say(' ' + keys.map(k => k.padStart(13)).join('')); for (const r of rows) { say(' ' + keys.map(k => { let v = r[k]; if (k === 'rep3minUvRate' || k === 'uctr' || k === 'uctcvr') v = (v === undefined || v === null) ? '-' : (v * 100).toFixed(1) + '%'; return String(v ?? '-').padStart(13); }).join('')); } // 汇总校验 const sum = (k) => rows.reduce((a, b) => a + (Number(b[k]) || 0), 0); say(` SUM payAmt=${sum('payAmt').toFixed(2)} payOrdCnt=${sum('payOrdCnt')} showPv=${sum('showPv')} vstPv=${sum('vstPv')} ipv=${sum('ipv')} chatUv=${sum('chatUv')}`); } // 分布类 if (/browse/.test(api)) { const dists = deepFind(json, 'distributionList'); const anyArr = []; const walkArr = (o, p) => { if (!o || typeof o !== 'object') return; for (const k of Object.keys(o)) { if (Array.isArray(o[k]) && o[k].length && typeof o[k][0] === 'object') anyArr.push([p + '.' + k, o[k]]); else walkArr(o[k], p + '.' + k); } }; walkArr(json.data, 'data'); say(' --- browse 分布数组 ---'); for (const [p, arr] of anyArr.slice(0, 12)) { say(` ${p} len=${arr.length}`); say(' ' + JSON.stringify(arr.slice(0, 12)).slice(0, 1200)); } } if (/flow\.detail/.test(api)) { const t = deepFind(json, 'itemFlowTransferData')[0]; say(' --- flow itemFlowTransferData ---'); if (t) for (const k of Object.keys(t.value)) { const v = t.value[k]; say(` ${k.padEnd(20)} now=${String(v?.dataStr).padEnd(12)} prev=${String(v?.lastDataStr).padEnd(12)} Δ=${String(v?.ratioFormat).padEnd(10)}`); } } } fs.writeFileSync(outFile, L.join('\n'), 'utf8'); console.log('written: ' + outFile + ' lines=' + L.length);