Files
HealthLog/tests/integration/pulse-comparison-availability.test.ts
Marc-André Bombeck 8f730484e0 Average pulse by day in the comparison snapshot and Coach inventory
The insights comparison snapshot took its current and baseline pulse figures
from a reading AVG, and the Coach's data inventory told the model a reading
mean of the whole pulse history. Both now read the mean of the user's local
days, each the mean of its hours' means, through the day-mean helper. Every
other type keeps its aggregate.
2026-10-04 11:37:57 +02:00

92 lines
3.0 KiB
TypeScript

/**
* The comparison snapshot's window average and the Coach's availability mean
* read pulse as the mean of its days, each the mean of its hours' means, on a
* real Postgres.
*
* Two days, three days back and two days back (inside the 30-day window): a
* workout day with one hour of 12 readings at 150 and three resting hours of
* one reading at 60 (day value 82.5), and a resting day of two hours at 70.
* Mean of the days: 76.25. Mean of the readings: (12 x 150 + 3 x 60 + 2 x 70)
* / 17 = 124.7.
*/
import { beforeAll, describe, expect, it } from "vitest";
import { getPrismaClient, truncateAllTables } from "./setup";
import type { Prisma } from "@/generated/prisma/client";
import { probeCoachAvailability } from "@/lib/ai/coach/tools/availability";
import { hourlyMeanWindowAverage } from "@/lib/insights/comprehensive-generate";
import { invalidateUserTimezone } from "@/lib/tz/resolver";
const prisma = getPrismaClient();
const USER = "pulse-comparison-availability";
const DAY = 86_400_000;
const now = new Date();
const D1 = Date.UTC(
now.getUTCFullYear(),
now.getUTCMonth(),
now.getUTCDate() - 3,
);
const D2 = D1 + DAY;
const DAY_MEAN = (82.5 + 70) / 2;
const READING_MEAN = (12 * 150 + 3 * 60 + 2 * 70) / 17;
beforeAll(async () => {
await truncateAllTables(prisma);
await prisma.user.create({
data: { id: USER, username: USER, timezone: "UTC" },
});
invalidateUserTimezone(USER);
const rows: Prisma.MeasurementCreateManyInput[] = [];
const add = (at: number, value: number) =>
rows.push({
id: `${USER}-${rows.length}`,
userId: USER,
type: "PULSE",
unit: "bpm",
source: "APPLE_HEALTH",
value,
measuredAt: new Date(at),
});
for (let i = 0; i < 12; i += 1) add(D1 + 10 * 3_600_000 + i * 300_000, 150);
for (const h of [12, 14, 16]) add(D1 + h * 3_600_000, 60);
for (const h of [8, 9]) add(D2 + h * 3_600_000, 70);
await prisma.measurement.createMany({ data: rows });
}, 120_000);
describe("pulse window means on a real database", () => {
it("the fixture separates the two statistics", () => {
expect(READING_MEAN).toBeCloseTo(124.71, 2);
expect(DAY_MEAN).toBe(76.25);
});
it("the comparison snapshot's window average is the mean of the days", async () => {
const avg = await hourlyMeanWindowAverage(USER, "PULSE", {
gt: new Date(Date.now() - 30 * DAY),
});
expect(avg).toBeCloseTo(DAY_MEAN, 9);
const empty = await hourlyMeanWindowAverage(USER, "PULSE", {
gt: new Date(Date.now() - 60 * DAY),
lte: new Date(Date.now() - 30 * DAY),
});
expect(empty).toBeNull();
});
it("the Coach availability mean is the mean of the days", async () => {
const probed = await probeCoachAvailability(
USER,
new Map([["pulse", { kind: "measurement", types: ["PULSE"] }]]),
{ now },
);
const series = probed.get("pulse")?.series;
expect(series).toEqual([
expect.objectContaining({
series: "PULSE",
count: 17,
mean: Math.round(DAY_MEAN * 10) / 10,
min: 60,
max: 150,
}),
]);
});
});