( daily: DailyRawPoint[], rangeStartIso?: string, rangeEndIso?: string, )
| 104 | } |
| 105 | |
| 106 | export function buildYearlyEvolution( |
| 107 | daily: DailyRawPoint[], |
| 108 | rangeStartIso?: string, |
| 109 | rangeEndIso?: string, |
| 110 | ): YearlyDataPoint[] { |
| 111 | const sorted = sortedDaily(daily) |
| 112 | const byYear = new Map<string, number>() |
| 113 | for (const item of sorted) { |
| 114 | const y = item.day.slice(0, 4) |
| 115 | byYear.set(y, (byYear.get(y) ?? 0) + item.value) |
| 116 | } |
| 117 | |
| 118 | const entries = Array.from(byYear.entries()).sort(([a], [b]) => a.localeCompare(b)) |
| 119 | |
| 120 | return entries.map(([year, value], i) => { |
| 121 | const total = daysInYear(Number(year)) |
| 122 | const yearStart = parseIsoDate(`${year}-01-01`) |
| 123 | const isFirst = i === 0 |
| 124 | const isLast = i === entries.length - 1 |
| 125 | |
| 126 | const startOffset = |
| 127 | isFirst && rangeStartIso |
| 128 | ? Math.floor((parseIsoDate(rangeStartIso).getTime() - yearStart.getTime()) / DAY_MS) |
| 129 | : 0 |
| 130 | const endOffset = |
| 131 | isLast && rangeEndIso |
| 132 | ? Math.floor((parseIsoDate(rangeEndIso).getTime() - yearStart.getTime()) / DAY_MS) + 1 |
| 133 | : total |
| 134 | const actualDays = endOffset - startOffset |
| 135 | |
| 136 | if (actualDays < total) value = fillPartialBucket(value, actualDays, total) |
| 137 | |
| 138 | return { year, value, timestamp: yearStart.getTime() } |
| 139 | }) |
| 140 | } |
no test coverage detected