mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
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.
92 lines
3.0 KiB
TypeScript
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,
|
|
}),
|
|
]);
|
|
});
|
|
});
|