import type { ConsumptionChunk, DailyConsumption, Totals } from "./types.ts"; /** * Folds chunks into days, newest day first, each with its running lifetime total. * Pure, so the arithmetic is tested without a database. */ export function aggregateDays(chunks: ConsumptionChunk[]): DailyConsumption[] { const byDate = new Map(); for (const chunk of chunks) { let day = byDate.get(chunk.date); if (!day) { day = { installationId: chunk.installationId, date: chunk.date, recordedAt: chunk.recordedAt, day: { km: 0, kwh: 0, hours: 0, socDrop: 0 }, sohAt: -Infinity, }; byDate.set(chunk.date, day); } day.day.km += chunk.day.km; day.day.kwh += chunk.day.kwh; day.day.hours += chunk.day.hours; day.day.socDrop += chunk.day.socDrop; const at = chunk.recordedAt.getTime(); if (at > day.recordedAt.getTime()) day.recordedAt = chunk.recordedAt; // The newest manual SoH of the day wins. if (chunk.sohPercent !== undefined && at > day.sohAt) { day.sohPercent = chunk.sohPercent; day.sohAt = at; } } const ascending = [...byDate.values()].sort((a, b) => a.date.localeCompare(b.date)); const running: Totals = { km: 0, kwh: 0, hours: 0, socDrop: 0 }; const days = ascending.map(({ sohAt: _sohAt, ...day }) => { running.km += day.day.km; running.kwh += day.day.kwh; running.hours += day.day.hours; running.socDrop += day.day.socDrop; return { ...day, lifetime: { ...running } }; }); return days.reverse(); }