mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
The v1.9.0 time-range work imported ANALYTICS_RANGES as a value into the time-range pills and the insights layout-prefs hook from range-delta.ts. That module also pulls the rollup readers and the Postgres client, so the value import dragged pg (dns/fs/net/tls) into the browser bundle and broke the production build with eight "Module not found" errors. Move the client-safe pieces — the range list, its type, the per-range window length, and the period-over-period result shapes — into a new range-shared module that imports nothing server-side. The insights client components import from there; range-delta.ts re-exports the same names so the server route and the OpenAPI registry keep their import paths. The window-composition logic and the rollup/db imports stay server-side. No runtime behaviour change — a pure module-boundary split. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 75124e76c1e987d8a022fb39d891049340199b38)
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
/**
|
|
* v1.9.0 — client-safe range constants and types for the Insights metric
|
|
* pages.
|
|
*
|
|
* The user-selectable range list, its type, the per-range window length, and
|
|
* the period-over-period result shapes are shared between the client (the
|
|
* pills, the delta caption, the range hook, the layout-prefs hook) and the
|
|
* server (`/api/analytics/range` + `range-delta.ts`). This module imports
|
|
* NOTHING server-side — no rollup readers, no `db`, no Prisma client — so the
|
|
* client bundle never transitively pulls `pg` (dns/fs/net/tls) through a range
|
|
* import. The server-only window-composition logic lives in `range-delta.ts`,
|
|
* which re-exports these names for its own callers.
|
|
*/
|
|
|
|
/** The user-selectable ranges. */
|
|
export const ANALYTICS_RANGES = ["7d", "30d", "90d", "1y"] as const;
|
|
export type AnalyticsRange = (typeof ANALYTICS_RANGES)[number];
|
|
|
|
/** Trailing-window length, in days, for each range. */
|
|
const RANGE_WINDOW_DAYS: Record<AnalyticsRange, number> = {
|
|
"7d": 7,
|
|
"30d": 30,
|
|
"90d": 90,
|
|
"1y": 365,
|
|
};
|
|
|
|
export function rangeWindowDays(range: AnalyticsRange): number {
|
|
return RANGE_WINDOW_DAYS[range];
|
|
}
|
|
|
|
/** A composed window aggregate. `mean`/etc. are null when the window is empty. */
|
|
export interface WindowAggregate {
|
|
count: number;
|
|
min: number | null;
|
|
max: number | null;
|
|
mean: number | null;
|
|
sum: number | null;
|
|
}
|
|
|
|
export interface RangeDeltaResult {
|
|
range: AnalyticsRange;
|
|
windowDays: number;
|
|
/** The granularity the underlying rollup read resolved against. */
|
|
granularity: string;
|
|
current: WindowAggregate;
|
|
previous: WindowAggregate;
|
|
/**
|
|
* `current.mean - previous.mean`, or null when either window has no mean
|
|
* (no data in the current or prior window) — never a misleading 0.
|
|
*/
|
|
delta: number | null;
|
|
/**
|
|
* `delta / previous.mean` as a fraction (0.03 = +3 %), or null when the
|
|
* prior window has no mean / a zero mean (no divide-by-zero, no misleading
|
|
* 0 %). The client renders "no prior-period data" in that case.
|
|
*/
|
|
deltaPct: number | null;
|
|
}
|