mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
Merge branch 'fix/v1437-review' into release/v1.43.7
# Conflicts: # src/app/api/analytics/route.ts
This commit is contained in:
commit
45c027aee2
217 files changed
+3636
-2127
No files matched your search
@@ -18,6 +18,7 @@
|
||||
* to the derived contract cannot leave this document behind.
|
||||
*/
|
||||
import { z } from "zod/v4";
|
||||
import { recoveryEmptySchema } from "./recovery-empty";
|
||||
import type { ZodOpenApiObject } from "zod-openapi";
|
||||
|
||||
import { SCORE_PILLAR_IDS } from "@/lib/analytics/score/types";
|
||||
@@ -140,11 +141,12 @@ const analyticsSummariesSlice = z
|
||||
),
|
||||
lastSeenByType,
|
||||
sleepSourceDiscrepancy,
|
||||
recoveryEmpty: recoveryEmptySchema,
|
||||
})
|
||||
.meta({
|
||||
id: "AnalyticsSummariesSlice",
|
||||
description:
|
||||
"What `?slice=summaries` answers: the per-type summaries and their freshness, and nothing thick. Four keys, and the caller gets them from two SQL passes rather than the default slice's chain.",
|
||||
"What `?slice=summaries` answers: the per-type summaries and their freshness, the recovery page's empty reason, and nothing thick. Five keys, and the caller gets them from two SQL passes rather than the default slice's chain.",
|
||||
});
|
||||
|
||||
// ── Correlations ─────────────────────────────────────────────────────
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Schemas come from `src/lib/validations/*` where shared with the
|
||||
* runtime request parsing, so the wire contract stays single-source.
|
||||
*/
|
||||
import { recoveryEmptySchema } from "../recovery-empty";
|
||||
import { trendSentiment, trendVerdict } from "../trend-verdict";
|
||||
import { z } from "zod/v4";
|
||||
import { measurementSourceEnum, measurementTypeEnum } from "../shared";
|
||||
@@ -324,7 +325,7 @@ export const sleepScoreValue = z
|
||||
.enum(["few_nights", "no_main_sleep"])
|
||||
.optional()
|
||||
.describe(
|
||||
"Only on a null `consistency` / `timing`: why it is withheld. `few_nights` — fewer than three nights in the window have a main sleep on the clock (count in `rhythmNights`); `no_main_sleep` — the latest day had only a daytime nap. Absent otherwise.",
|
||||
"Only on a null `consistency` / `timing`: why it is withheld. `few_nights` — fewer than `rhythmMinNights` nights in the window have a main sleep on the clock (count in `rhythmNights`); `no_main_sleep` — the latest day had only a daytime nap. Absent otherwise.",
|
||||
),
|
||||
}),
|
||||
)
|
||||
@@ -336,6 +337,12 @@ export const sleepScoreValue = z
|
||||
.describe(
|
||||
"Nights in the window with a main sleep placed on the clock — what consistency and timing are computed from. A daytime nap is not one; a short night is.",
|
||||
),
|
||||
rhythmMinNights: z
|
||||
.number()
|
||||
.int()
|
||||
.describe(
|
||||
'How many `rhythmNights` consistency and timing need before they are scored; below it they carry `gap: "few_nights"`.',
|
||||
),
|
||||
series: z
|
||||
.array(z.number())
|
||||
.describe(
|
||||
@@ -2032,6 +2039,7 @@ export const analyticsSummariesSliceResponse = z
|
||||
),
|
||||
lastSeenByType: z.record(z.string(), lastSeenSlot),
|
||||
sleepSourceDiscrepancy,
|
||||
recoveryEmpty: recoveryEmptySchema,
|
||||
})
|
||||
.meta({
|
||||
id: "AnalyticsSummariesSlice",
|
||||
|
||||
@@ -465,7 +465,7 @@ const sleepNightResponse = z
|
||||
.meta({
|
||||
id: "SleepNightResource",
|
||||
description:
|
||||
"One reconstructed sleep night: the main session's hypnogram segments + breakdown, plus same-wake-day naps surfaced separately.",
|
||||
"One reconstructed sleep night: the main session's hypnogram segments + breakdown, plus same-wake-day naps surfaced separately. A session is a nap when it holds under three hours asleep centred between 10:00 and 20:00 local time; the main session is the longest session that is not a nap. A day whose only sleep is naps has `main: null` and its naps.",
|
||||
});
|
||||
|
||||
// ── Sleep rhythm (sleep-debt + chronotype, v1.17.0) ──────────────────
|
||||
@@ -804,7 +804,7 @@ export const measurementPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
summary: "List measurements",
|
||||
description:
|
||||
"Filter by type + date range. Response is paged via offset/limit; default limit 100.\n\n" +
|
||||
"With `groupBy=day` on a cumulative type the rows are one sum per local day, and `limit` / `offset` page over those days; every returned day is complete. `type=SLEEP_DURATION` returns one row per night, paged the same way over nights. On both, `meta.total` counts the days or nights in the filtered window. A day-sum page takes `limit` as asked (up to the list's 5000); a sleep page holds at most 1000 nights, and `meta.limit` echoes the value applied.",
|
||||
"With `groupBy=day` on a cumulative type the rows are one sum per local day, and `limit` / `offset` page over those days; every returned day is complete, which is why `valueMin` / `valueMax` (a filter on single readings) are refused with 422 beside `groupBy=day`. `type=SLEEP_DURATION` returns one row per night, paged the same way over nights; every night of the page has a row, and a day whose only sleep is daytime naps carries `value` 0 with its naps in `napCount` / `napAsleepMinutes`. Its `dayKey` is the same day `/api/sleep/night?date=` answers for. On both, `meta.total` counts the days or nights in the filtered window. A day-sum page takes `limit` as asked (up to the list's 5000); a sleep page holds at most 1000 nights, and `meta.limit` echoes the value applied.",
|
||||
requestParams: {
|
||||
query: listMeasurementsSchema,
|
||||
},
|
||||
@@ -1118,7 +1118,7 @@ export const measurementPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
tags: ["Measurements"],
|
||||
summary: "Reconstructed sleep night (hypnogram source, v1.11.5)",
|
||||
description:
|
||||
"Returns one night's reconstructed sleep session for the phase-progression (hypnogram) view: the canonical source's stage segments (each with an absolute start/end), the per-stage breakdown, asleep/in-bed/awake totals, the mid-sleep awakenings count, and same-wake-day naps surfaced separately. Session-clustered, keyed by the local wake day, collapsed to one source via the sleep priority ladder so two sources never overlay. `date` omitted returns the most recent night. A read-only view over existing per-stage SLEEP_DURATION rows — no schema, no new measurement type.",
|
||||
"Returns one night's reconstructed sleep session for the phase-progression (hypnogram) view: the canonical source's stage segments (each with an absolute start/end), the per-stage breakdown, asleep/in-bed/awake totals, the mid-sleep awakenings count, and same-wake-day naps surfaced separately. Session-clustered, keyed by the local wake day, collapsed to one source via the sleep priority ladder so two sources never overlay. `date` omitted returns the most recent night holding sleep, however old. Any past date is answered, with no window cut. A read-only view over existing per-stage SLEEP_DURATION rows — no schema, no new measurement type.",
|
||||
requestParams: {
|
||||
query: z.object({
|
||||
date: z
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod/v4";
|
||||
import { RECOVERY_EMPTY_REASONS } from "@/lib/insights/recovery-empty";
|
||||
|
||||
/** Why the recovery page is empty; shared by both slim-slice schemas. */
|
||||
export const recoveryEmptySchema = z
|
||||
.enum(RECOVERY_EMPTY_REASONS)
|
||||
.nullable()
|
||||
.describe(
|
||||
"Why the recovery page has nothing to chart, resolved on the server; null when any recovery type (ANS_CHARGE, DAY_STRAIN, WORKOUT_STRAIN, CARDIO_LOAD, AVERAGE_HEART_RATE, MAX_HEART_RATE, ENERGY_EXPENDITURE_KJ, RESILIENCE) has a reading. `oura_permission`: Oura is connected and its grant leaves out the scope the resilience level needs, one reconnect fixes it. `oura_no_resilience`: Oura is connected and no level has arrived yet. `general`: anyone else. Clients render the empty note from this value and never work it out from the connection status.",
|
||||
);
|
||||
Reference in new issue
Block a user