/** Pure math for the relationship-pulse ribbon (Client 360 overview). */ export interface PulseEvent { id: string lane: number date: string // ISO yyyy-mm-dd label: string tone: 'accent' | 'ok' | 'warn' | 'err' } export const LANES = ['Documents', 'Payments', 'Interactions', 'AMC & renewals'] as const /** n month keys ('YYYY-MM') ending at todayIso's month, oldest first. */ export function lastMonths(todayIso: string, n = 12): string[] { const year = Number(todayIso.slice(0, 4)) const month = Number(todayIso.slice(5, 7)) // 1-based const out: string[] = [] for (let i = n - 1; i >= 0; i--) { const total = year * 12 + (month - 1) - i const y = Math.floor(total / 12) const m = total % 12 + 1 out.push(`${y}-${String(m).padStart(2, '0')}`) } return out } /** Bucket events into `${lane}:${monthIndex}` cells; out-of-window events are dropped. */ export function groupPulse(events: PulseEvent[], months: string[]): Map { const index = new Map() months.forEach((m, i) => index.set(m, i)) const out = new Map() for (const ev of events) { const mi = index.get(ev.date.slice(0, 7)) if (mi === undefined) continue const key = `${ev.lane}:${mi}` const cell = out.get(key) if (cell !== undefined) cell.push(ev) else out.set(key, [ev]) } return out }