export type MomentAsset = { id: string; capture_ts_utc: string; }; export type MomentCluster = { day: string; start: string; end: string; count: number; assets: MomentAsset[]; }; const MOMENT_WINDOW_MINUTES = 30; const MOMENT_WINDOW_MS = MOMENT_WINDOW_MINUTES * 60 * 1000; function dayKeyFromIso(iso: string) { const d = new Date(iso); const yyyy = d.getUTCFullYear(); const mm = String(d.getUTCMonth() + 1).padStart(2, "0"); const dd = String(d.getUTCDate()).padStart(2, "0"); return `${yyyy}-${mm}-${dd}`; } export function clusterMoments(input: MomentAsset[]): MomentCluster[] { const byDay = new Map(); for (const asset of input) { if (!asset.capture_ts_utc) continue; const key = dayKeyFromIso(asset.capture_ts_utc); const list = byDay.get(key); if (list) list.push(asset); else byDay.set(key, [asset]); } const clusters: MomentCluster[] = []; for (const [day, assets] of byDay) { const sorted = [...assets].sort((a, b) => a.capture_ts_utc.localeCompare(b.capture_ts_utc), ); let current: MomentAsset[] = []; let lastTs: number | null = null; for (const asset of sorted) { const ts = new Date(asset.capture_ts_utc).getTime(); if (!Number.isFinite(ts)) continue; if (lastTs === null || ts - lastTs <= MOMENT_WINDOW_MS) { current.push(asset); } else { const start = current[0]?.capture_ts_utc; const end = current[current.length - 1]?.capture_ts_utc; if (start && end) { clusters.push({ day, start, end, count: current.length, assets: current, }); } current = [asset]; } lastTs = ts; } if (current.length) { const start = current[0].capture_ts_utc; const end = current[current.length - 1].capture_ts_utc; clusters.push({ day, start, end, count: current.length, assets: current, }); } } return clusters; }