| 59 | |
| 60 | /** PDF report blocks (feed to pdf-lite buildPdf). PURE. */ |
| 61 | export function buildMaintainReportPdfBlocks(d: MaintainReportData): PdfBlock[] { |
| 62 | const s = d.stats; |
| 63 | const kv = (k: string, v: string): PdfBlock => ({ text: `${k}: ${v}`, size: 11, indent: 10 }); |
| 64 | const blocks: PdfBlock[] = [ |
| 65 | { text: `Self-Improvement Report${d.project ? ` — ${d.project}` : ''}`, size: 18, bold: true }, |
| 66 | { text: `Generated ${d.generatedAt} by QodeX maintain. Every number comes from verified-run receipts — filesChanged from real git diffs, verification from checkers that actually ran.`, size: 9, spaceBefore: 4 }, |
| 67 | { text: 'All time', size: 13, bold: true, spaceBefore: 12 }, |
| 68 | kv('Cleanup PRs shipped', String(s.opened)), |
| 69 | kv('Safely blocked (guardrail declined)', String(s.blocked)), |
| 70 | kv('Files cleaned', String(s.filesCleaned)), |
| 71 | kv('Est. minutes saved', `~${s.estMinutesSaved}`), |
| 72 | kv('Success rate', `${Math.round(s.successRate * 100)}% of ${s.totalRuns} runs`), |
| 73 | { text: 'This week', size: 13, bold: true, spaceBefore: 10 }, |
| 74 | kv('Opened', `${d.weekly.opened} PR(s) · ${d.weekly.filesCleaned} file(s) · ${d.weekly.openedDelta >= 0 ? '+' : '-'}${Math.abs(d.weekly.openedDelta)} vs last week`), |
| 75 | { text: '8-week trend (opened/week)', size: 13, bold: true, spaceBefore: 10 }, |
| 76 | { text: '', bars: d.trend, size: 9 }, |
| 77 | kv('Forecast', `${dirWord(d.forecast.direction)} - avg ~${d.forecast.weeklyAvg}/wk - next week ~ ${d.forecast.nextWeek}`), |
| 78 | kv('Projected', `~${d.projection.cleanupsPerMonth} cleanups/mo - ~${d.projection.minutesPerMonth} min/mo`), |
| 79 | { text: 'By scope', size: 13, bold: true, spaceBefore: 10 }, |
| 80 | ...(s.byScope.length ? s.byScope.map(x => kv(x.scope, `${x.opened} opened / ${x.runs} runs`)) : [{ text: 'no runs yet', size: 10, indent: 10 }]), |
| 81 | ]; |
| 82 | if (d.next) blocks.push({ text: 'Suggested next', size: 13, bold: true, spaceBefore: 10 }, kv(d.next.scope, d.next.why)); |
| 83 | return blocks; |
| 84 | } |