trainlog/web/src/routes/sleepDiaryFacts.ts

62 lines
2 KiB
TypeScript

import type { SleepEntry, SleepEvent } from "../api/sleepDiary";
import { projectSleepTimeline } from "./sleepVisualProjection";
export interface SleepEntryFacts {
bedTime: string | null;
finalGetUp: string | null;
sleepSeconds: number;
timeInBedSeconds: number | null;
longAwakeCount: number;
longAwakeSeconds: number;
napCount: number;
napSeconds: number;
sleepinessCount: number;
}
const durationSeconds = (event: SleepEvent): number => {
if (event.end_at === null) return 0;
const start = Date.parse(event.start_at);
const end = Date.parse(event.end_at);
return Number.isFinite(start) && Number.isFinite(end) && end > start
? Math.floor((end - start) / 1000)
: 0;
};
const clock = (timestamp: string | null): string | null =>
timestamp === null ? null : timestamp.slice(11, 16);
export function sleepEntryFacts(entry: SleepEntry): SleepEntryFacts {
const projection = projectSleepTimeline(entry);
const bedtime = entry.events.find((event) => event.type === "bed_time");
const finalGetUp = [...entry.events]
.reverse()
.find((event) => event.type === "final_get_up");
const interval =
bedtime !== undefined && finalGetUp !== undefined
? Date.parse(finalGetUp.start_at) - Date.parse(bedtime.start_at)
: Number.NaN;
const events = (type: SleepEvent["type"]) =>
entry.events.filter((event) => event.type === type);
const longAwake = events("long_awake");
const naps = events("nap");
return {
bedTime: clock(bedtime?.start_at ?? null),
finalGetUp: clock(finalGetUp?.start_at ?? null),
sleepSeconds: projection.sleepSeconds,
timeInBedSeconds:
Number.isFinite(interval) && interval > 0
? Math.floor(interval / 1000)
: null,
longAwakeCount: longAwake.length,
longAwakeSeconds: longAwake.reduce(
(total, event) => total + durationSeconds(event),
0,
),
napCount: naps.length,
napSeconds: naps.reduce(
(total, event) => total + durationSeconds(event),
0,
),
sleepinessCount: events("daytime_sleepiness").length,
};
}