/* M1 统计:node ml/gen/stats.cjs ml/data/shard_*.jsonl > ml/data/stats.json */ 'use strict'; const fs = require('fs'); const files = process.argv.slice(2); const by = {}; let total = 0, keep = 0, steps = 0, samples = 0, padTail = 0; const q = a => { a.sort((x, y) => x - y); return a.length ? { p10: +a[Math.floor(a.length * 0.1)].toFixed(1), p50: +a[a.length >> 1].toFixed(1), p90: +a[Math.floor(a.length * 0.9)].toFixed(1), max: +a[a.length - 1].toFixed(1) } : null; }; const plan = [], certMs = [], travel = [], exec = [], gaps = []; for (const f of files) for (const line of fs.readFileSync(f, 'utf8').split('\n')) { if (!line) continue; const r = JSON.parse(line); total++; const k = r.cat + '/' + r.mode; const b = by[k] = by[k] || { n: 0, reached: 0, certFail: 0, keep: 0, demoted: 0, consensusDrop: 0, j6Frozen: 0 }; b.n++; if (r.reached) b.reached++; if (r.cert && !r.cert.ok) b.certFail++; if (r.demoted) b.demoted++; if (r.why === 'consensus') b.consensusDrop++; if (r.j6Frozen) b.j6Frozen++; if (r.plan_ms != null) plan.push(r.plan_ms); if (r.cert) certMs.push(r.cert.ms); if (r.cert) gaps.push(r.cert.minGap); if (r.keep) { b.keep++; keep++; travel.push(r.travel_deg); exec.push(r.exec_s); const n = r.traj.length - 1; steps += n; const s = Math.floor(n / 3) + 1; samples += s; for (let t = 0; t <= n; t += 3) if (t + 16 > n) padTail++; } } const out = { tasks: total, keep, keepRate: +(keep / total).toFixed(3), byCatMode: by, planMs: q(plan), certMs: q(certMs), vertexMinGapMm: q(gaps), travelDeg: q(travel), execS: q(exec), samplesEvery3: samples, samplesPerTask: +(samples / keep).toFixed(1), paddedSampleShare: +(padTail / samples).toFixed(3), projectedSamplesFor50k: Math.round(samples / total * 50000), consensusDrop: Object.values(by).reduce((a, b) => a + b.consensusDrop, 0) }; console.log(JSON.stringify(out, null, 1));