mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
refactor(measurements): move the list modes out of the route and page every night
The 1500-line list route carried the day-grouped list, the sleep list and its SQL night page, each with its own copy of the window-total paging. They now live in src/lib/measurements/list/ with one paging helper, and the route dispatches. The sleep list gives every page night a row. A night whose reconstruction held no main sleep (a day of naps alone) used to be dropped while meta.total still counted it, which left short pages; its row now carries 0 minutes and its naps. The SQL night cut splices the asleep stages from ASLEEP_STAGES and folds rows into hour buckets before the window functions, which cannot move a session boundary and sorts a handful of rows per night instead of every stage. A drill-down reads its night the way the night view does. groupBy=day refuses valueMin / valueMax with 422: a value range filters single readings, and the rows promise whole days. An integration test runs the SQL page and reconstructSleepSessions over one set of awkward shapes and holds them to the same nights, and the list, the night view and the series to one answer per day. The unit test of the day-grouped branch now pins only the branch; what the rows hold is pinned against Postgres.
This commit is contained in:
1 parent
6328117bd2
commit
3da72136ba
10 files changed
+1178
-841
No files matched your search
@@ -589,7 +589,7 @@ export const listMeasurementsSchema = z
|
||||
// Omitting `aggregate` keeps the raw wire shape (iOS contract); the
|
||||
// chart-data client must opt in explicitly.
|
||||
aggregate: z.enum(["raw", "daily", "weekly", "monthly"]).optional(),
|
||||
// v1.4.36 W1 — opt-in source switch for daily-aggregate reads. When
|
||||
// v1.4.36 — opt-in source switch for daily-aggregate reads. When
|
||||
// `source=rollup` + `aggregate=daily`, the route reads from the
|
||||
// persistent `measurement_rollups` DAY buckets instead of running a
|
||||
// live `date_trunc` GROUP BY scan over the raw `measurements` table.
|
||||
@@ -612,7 +612,7 @@ export const listMeasurementsSchema = z
|
||||
// `value: { gte, lte }` range. Server-authoritative; validated min<=max.
|
||||
valueMin: z.coerce.number().finite().optional(),
|
||||
valueMax: z.coerce.number().finite().optional(),
|
||||
// v1.4.37 W7c — list-view "one row per day" mode for cumulative
|
||||
// v1.4.37 — list-view "one row per day" mode for cumulative
|
||||
// types (steps, active energy, distance, flights, daylight). When
|
||||
// `groupBy=day` is set and `type` is a cumulative HK type, the route
|
||||
// returns one synthesised row per user-TZ day with `value` = SUM
|
||||
@@ -623,18 +623,18 @@ export const listMeasurementsSchema = z
|
||||
// picks the page's local days in Postgres and reports the distinct-day
|
||||
// count as `meta.total` (#1245).
|
||||
groupBy: z.enum(["day"]).optional(),
|
||||
// v1.4.37 W7c — drill-down to per-sample rows for a single day in
|
||||
// v1.4.37 — drill-down to per-sample rows for a single day in
|
||||
// the user's IANA timezone. Format `YYYY-MM-DD`; the route resolves
|
||||
// the day boundary against the user's `User.timezone`. Used by the
|
||||
// expandable list row to reveal the chunks that contributed to the
|
||||
// collapsed daily total.
|
||||
//
|
||||
// v1.4.37 W10 — a drill-down returns one bounded page rather than a
|
||||
// v1.4.37 — a drill-down returns one bounded page rather than a
|
||||
// cursor, so it refuses a non-zero `offset`.
|
||||
dayKey: z
|
||||
.string()
|
||||
.regex(/^\d{4}-\d{2}-\d{2}$/, "dayKey must be YYYY-MM-DD")
|
||||
// v1.4.37 W10 — a `YYYY-MM-DD` string can satisfy the regex while
|
||||
// v1.4.37 — a `YYYY-MM-DD` string can satisfy the regex while
|
||||
// still being an impossible calendar date (`2026-02-30`,
|
||||
// `2026-13-01`). `new Date("2026-02-30T00:00:00Z")` silently
|
||||
// overflows to March 2, so the drill-down would return rows from
|
||||
@@ -665,6 +665,19 @@ export const listMeasurementsSchema = z
|
||||
"limit must be <= 1000 when dayKey is set; drill-down responses are capped at 1000 rows",
|
||||
path: ["limit"],
|
||||
})
|
||||
// A value range filters single readings, and a day-grouped row is the sum
|
||||
// of a whole day: filtering its readings would return partial days summed
|
||||
// as if they were whole. The combination has no honest answer, so it is
|
||||
// refused rather than served.
|
||||
.refine(
|
||||
({ groupBy, valueMin, valueMax }) =>
|
||||
groupBy !== "day" || (valueMin == null && valueMax == null),
|
||||
{
|
||||
message:
|
||||
"valueMin / valueMax filter single readings and cannot be combined with groupBy=day",
|
||||
path: ["groupBy"],
|
||||
},
|
||||
)
|
||||
// v1.18.5 — an inverted value range (`valueMin > valueMax`) is a client
|
||||
// bug, not an empty result set; reject it at the validator with a 422 so
|
||||
// the filter UI can surface the mismatch instead of silently returning
|
||||
|
||||
Reference in new issue
Block a user