Surface sleep-debt and chronotype on the sleep page

Compute cumulative sleep debt and the MCTQ chronotype server-side off the
canonical night reconstruction the Sleep Score reads, and render them on
the sleep page: a sleep-debt headline with a calm partial state under the
night threshold, and a chronotype card whose social-jetlag detail and
corrected mid-sleep sit behind an advanced disclosure. Both hold a learning
state on thin data and never assert a total or a band.

Serve the same values to the dashboard summary and a new GET /api/sleep/rhythm
read so the native client renders identical figures. Free vs work nights
default to weekend in the user's timezone until day-type tracking exists.
This commit is contained in:
Marc-André Bombeck committed 2026-06-14 19:03:11 +02:00
1 parent 4884f019b7
commit d1ba01548f
22 files changed
+1489 -2

No files matched your search

+149
View File
@@ -1906,6 +1906,27 @@ paths:
"401": *a1
"422": *a3
"429": *a4
/api/sleep/rhythm:
get:
tags:
- Measurements
summary: Sleep rhythm — sleep-debt + chronotype (v1.17.0)
description: "Returns the two server-authoritative sleep-timing signals the Sleep page + iOS render off the same
canonical night reconstruction the Sleep Score uses: cumulative `sleepDebt` over the rolling window (calm
`partial` state under the night threshold) and MCTQ `chronotype` (MSF/MSFsc band + social jetlag, `learning`
state until enough free-day nights exist). Free vs work nights default to weekend = free in the user's timezone
(no work calendar). A read-only view over existing per-stage SLEEP_DURATION rows — no schema, no new measurement
type."
responses:
"200":
description: Sleep-debt + chronotype DTO.
content:
application/json:
schema:
$ref: "#/components/schemas/SleepRhythmResponse"
"401": *a1
"422": *a3
"429": *a4
/api/workouts:
get:
tags:
@@ -9138,6 +9159,134 @@ components:
additionalProperties: false
description: "One reconstructed sleep night: the main session's hypnogram segments + breakdown, plus same-wake-day naps
surfaced separately."
SleepRhythmResponse:
type: object
properties:
data:
$ref: "#/components/schemas/SleepRhythmResource"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
SleepRhythmResource:
type: object
properties:
sleepDebt:
type: object
properties:
state:
type: string
enum:
- partial
- ready
description: "`partial` until enough tracked nights exist (calm 'still learning'); `ready` once the rolling window can
assert a cumulative debt."
debtMinutes:
type: integer
minimum: 0
maximum: 9007199254740991
description: Cumulative sleep debt over the window, in minutes, after caps.
needMinutes:
type: integer
minimum: 0
maximum: 9007199254740991
description: Age-based sleep need (minutes) used for the per-night deficit.
nightsCounted:
type: integer
minimum: 0
maximum: 9007199254740991
windowNights:
type: integer
exclusiveMinimum: 0
maximum: 9007199254740991
description: Rolling window length in nights.
nightsUntilReady:
type: integer
minimum: 0
maximum: 9007199254740991
description: Nights still needed before the debt is asserted (0 when ready).
required:
- state
- debtMinutes
- needMinutes
- nightsCounted
- windowNights
- nightsUntilReady
additionalProperties: false
chronotype:
type: object
properties:
state:
type: string
enum:
- learning
- ready
description: "`learning` until enough free-day nights exist (no band asserted); `ready` once the free-day sample is
large enough."
msfMinutes:
anyOf:
- type: number
- type: "null"
description: Mid-sleep on free days (minutes-of-day), null while learning.
msfScMinutes:
anyOf:
- type: number
- type: "null"
description: Sleep-debt-corrected mid-sleep on free days (MSFsc, minutes-of-day), null while learning.
band:
anyOf:
- type: string
enum:
- extreme_early
- early
- intermediate
- late
- extreme_late
- type: "null"
description: MCTQ chronotype band off MSFsc, null while learning.
socialJetlagMinutes:
anyOf:
- type: number
- type: "null"
description: Social jetlag = circular |MSF_work − MSF_free| in minutes, null when one side is missing.
freeNightsCounted:
type: integer
minimum: 0
maximum: 9007199254740991
workNightsCounted:
type: integer
minimum: 0
maximum: 9007199254740991
freeNightsUntilReady:
type: integer
minimum: 0
maximum: 9007199254740991
description: Free-day nights still needed before a band is asserted.
required:
- state
- msfMinutes
- msfScMinutes
- band
- socialJetlagMinutes
- freeNightsCounted
- workNightsCounted
- freeNightsUntilReady
additionalProperties: false
required:
- sleepDebt
- chronotype
additionalProperties: false
description: "Server-authoritative sleep-rhythm read: cumulative sleep debt over the rolling window + MCTQ chronotype
(MSF/MSFsc band, social jetlag). Free vs work nights default to weekend = free in the user's timezone (no work
calendar). A view over existing per-stage SLEEP_DURATION rows — no schema, no new type."
ListWorkoutsResponse:
type: object
properties: