(events: LearningEvent[], pendingCandidates: number)
| 55 | /** Aggregate the ledger (+ a live pending-candidate count) into dashboard stats. PURE |
| 56 | * over the event list so it's unit-testable; the I/O wrapper reads the file. */ |
| 57 | export function aggregateStats(events: LearningEvent[], pendingCandidates: number): LearningStats { |
| 58 | let captured = 0, promoted = 0, rejected = 0, merged = 0, confSum = 0, confN = 0; |
| 59 | for (const e of events) { |
| 60 | if (e.event === 'capture') { captured++; if (typeof e.confidence === 'number') { confSum += e.confidence; confN++; } } |
| 61 | else if (e.event === 'promote') promoted++; |
| 62 | else if (e.event === 'reject') rejected++; |
| 63 | else if (e.event === 'merge') merged++; |
| 64 | } |
| 65 | const decisions = promoted + rejected; |
| 66 | return { |
| 67 | captured, promoted, rejected, merged, |
| 68 | promotionRate: decisions ? promoted / decisions : 0, |
| 69 | avgConfidence: confN ? Math.round(confSum / confN) : null, |
| 70 | pendingCandidates, |
| 71 | lastEventAt: events.length ? events[events.length - 1]!.ts : null, |
| 72 | }; |
| 73 | } |
| 74 | |
| 75 | export async function readLearningEvents(): Promise<LearningEvent[]> { |
| 76 | try { |
no outgoing calls
no test coverage detected