mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import {
|
|
classifySleepDuration,
|
|
getSleepDurationRange,
|
|
} from "@/lib/analytics/classifications";
|
|
import { reconstructSleepNights } from "@/lib/analytics/sleep-night";
|
|
import { makeRangeClassifier, rollupConsistency } from "./consistency";
|
|
import type { TargetItem, TargetSleepStageRow, TargetTrend } from "./types";
|
|
|
|
interface SleepTargetInput {
|
|
sleepStageRows: TargetSleepStageRow[];
|
|
timezone: string;
|
|
sourcePriorityJson: unknown;
|
|
now: Date;
|
|
}
|
|
|
|
export function buildSleepTarget({
|
|
sleepStageRows,
|
|
timezone,
|
|
sourcePriorityJson,
|
|
now,
|
|
}: SleepTargetInput): TargetItem {
|
|
const sleepRange = getSleepDurationRange();
|
|
const nights = reconstructSleepNights(
|
|
sleepStageRows,
|
|
timezone,
|
|
sourcePriorityJson,
|
|
)
|
|
.filter((night) => night.asleepMinutes > 0)
|
|
.sort((a, b) => a.measuredAt.getTime() - b.measuredAt.getTime());
|
|
const roundHours = (value: number) => Math.round(value * 100) / 100;
|
|
const sleepEvents = nights.map((night) => ({
|
|
measuredAt: night.measuredAt,
|
|
value: roundHours(night.asleepMinutes / 60),
|
|
}));
|
|
const latestNight = nights.length > 0 ? nights[nights.length - 1] : null;
|
|
const current = latestNight
|
|
? roundHours(latestNight.asleepMinutes / 60)
|
|
: null;
|
|
const average30 =
|
|
sleepEvents.length > 0
|
|
? roundHours(
|
|
sleepEvents.reduce((sum, event) => sum + event.value, 0) /
|
|
sleepEvents.length,
|
|
)
|
|
: null;
|
|
|
|
let classification: TargetItem["classification"] = null;
|
|
if (current != null) {
|
|
const classified = classifySleepDuration(current);
|
|
classification = {
|
|
category: classified.category,
|
|
color: classified.color,
|
|
};
|
|
}
|
|
|
|
let trend: TargetTrend = null;
|
|
if (sleepEvents.length >= 4) {
|
|
const mid = Math.floor(sleepEvents.length / 2);
|
|
const firstHalf = sleepEvents.slice(0, mid);
|
|
const secondHalf = sleepEvents.slice(mid);
|
|
const averageFirst =
|
|
firstHalf.reduce((sum, event) => sum + event.value, 0) / firstHalf.length;
|
|
const averageSecond =
|
|
secondHalf.reduce((sum, event) => sum + event.value, 0) /
|
|
secondHalf.length;
|
|
const difference = averageSecond - averageFirst;
|
|
const threshold = averageFirst * 0.02;
|
|
if (difference > threshold) trend = "up";
|
|
else if (difference < -threshold) trend = "down";
|
|
else trend = "stable";
|
|
}
|
|
|
|
return {
|
|
type: "SLEEP_DURATION",
|
|
label: "Sleep duration",
|
|
current,
|
|
average30,
|
|
trend,
|
|
unit: "h",
|
|
range: sleepRange,
|
|
classification,
|
|
source: "AASM/SRS",
|
|
...rollupConsistency({
|
|
events: sleepEvents,
|
|
classify: makeRangeClassifier(sleepRange),
|
|
timezone,
|
|
now,
|
|
}),
|
|
};
|
|
}
|