( daily: DailyRawPoint[], rangeStartIso: string, rangeEndIso: string, )
| 26 | } |
| 27 | |
| 28 | export function buildWeeklyEvolution( |
| 29 | daily: DailyRawPoint[], |
| 30 | rangeStartIso: string, |
| 31 | rangeEndIso: string, |
| 32 | ): WeeklyDataPoint[] { |
| 33 | const sorted = sortedDaily(daily) |
| 34 | if (sorted.length === 0) return [] |
| 35 | |
| 36 | const rangeStartDate = parseIsoDate(rangeStartIso) |
| 37 | const rangeEndDate = parseIsoDate(rangeEndIso) |
| 38 | |
| 39 | const buckets = new Map<number, number>() |
| 40 | |
| 41 | for (const item of sorted) { |
| 42 | const itemDate = parseIsoDate(item.day) |
| 43 | const offset = Math.floor((rangeEndDate.getTime() - itemDate.getTime()) / DAY_MS) |
| 44 | if (offset < 0) continue |
| 45 | const idx = Math.floor(offset / 7) |
| 46 | buckets.set(idx, (buckets.get(idx) ?? 0) + item.value) |
| 47 | } |
| 48 | |
| 49 | return Array.from(buckets.entries()) |
| 50 | .sort(([a], [b]) => b - a) |
| 51 | .map(([idx, value]) => { |
| 52 | const weekEndDate = addDays(rangeEndDate, -(idx * 7)) |
| 53 | let weekStartDate = addDays(weekEndDate, -6) |
| 54 | |
| 55 | // First bucket may be partial |
| 56 | if (weekStartDate.getTime() < rangeStartDate.getTime()) { |
| 57 | weekStartDate = rangeStartDate |
| 58 | const actualDays = |
| 59 | Math.floor((weekEndDate.getTime() - rangeStartDate.getTime()) / DAY_MS) + 1 |
| 60 | value = fillPartialBucket(value, actualDays, 7) |
| 61 | } |
| 62 | |
| 63 | const weekStartIso = toIsoDate(weekStartDate) |
| 64 | const weekEndIso = toIsoDate(weekEndDate) |
| 65 | return { |
| 66 | value, |
| 67 | weekKey: `${weekStartIso}_${weekEndIso}`, |
| 68 | weekStart: weekStartIso, |
| 69 | weekEnd: weekEndIso, |
| 70 | timestampStart: weekStartDate.getTime(), |
| 71 | timestampEnd: weekEndDate.getTime(), |
| 72 | } |
| 73 | }) |
| 74 | } |
| 75 | |
| 76 | export function buildMonthlyEvolution( |
| 77 | daily: DailyRawPoint[], |
no test coverage detected