stats: add hourly views — continuous timeline + hour-of-day pattern

Closes #45.

Two new server-side aggregates in computeStats(), same JS-bucketing
approach as the existing by_day: by_hour (localHourBucket — literal clock
time, no business-day rollover, feeds a left-to-right timeline) and
by_hour_of_day (localHourOfDay — every day's hour 0-23 summed together,
zero-filled to a full 24-entry axis so a quiet hour reads as zero, not a
missing data point).

Client: two more revenue charts on /stats — 'Umsatz nach Stunde' (timeline)
and 'Umsatz nach Tageszeit' (pattern). Revenue only, not also a tx-count
variant, to keep the page from growing a chart per metric per granularity.

Build clean both sides. Manually verified by_hour_of_day returns all 24
zero-filled buckets against a fresh DB.
This commit is contained in:
iris 2026-07-30 20:14:45 +02:00
commit 6c2a938b91
3 changed files with 118 additions and 3 deletions

View file

@ -79,6 +79,27 @@ export function formatLocal(d: Date, tz: string = TZ): string {
return `${p.year}-${pad(p.month)}-${pad(p.day)} ${pad(p.hour)}:${pad(p.minute)}:${pad(p.second)}`;
}
/**
* Local wall-clock hour bucket as `YYYY-MM-DD HH:00`, for the continuous
* hourly timeline in /stats (#45) literal clock time, no business-day
* rollover (unlike businessDay() below): the point of this bucket is "what
* hour did this actually happen", not which sales night it counts toward.
*/
export function localHourBucket(d: Date, tz: string = TZ): string {
const p = localParts(d, tz);
const pad = (n: number) => String(n).padStart(2, '0');
return `${p.year}-${pad(p.month)}-${pad(p.day)} ${pad(p.hour)}:00`;
}
/**
* Local hour-of-day (0-23), for the "which hour is busiest" pattern
* summed across every day in /stats (#45) deliberately not business-day
* aware either, same reasoning as localHourBucket().
*/
export function localHourOfDay(d: Date, tz: string = TZ): number {
return localParts(d, tz).hour;
}
/**
* The business day a timestamp belongs to, as `YYYY-MM-DD`.
* Hours before BUSINESS_DAY_CUTOFF_HOUR are attributed to the previous day.