mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
The health-status read scanned five vitals over 90 days for a dated, sustained level shift. It now scans every vital with a stable daily level (adding RMSSD, diastolic pressure, pulse, body fat, respiratory rate, oxygen saturation, walking heart rate and VO2 max) over 180 days, reads one value per day per metric, leaves a metric unread when its module is off, and reports how many days stand on each side of the break. The Coach keeps its five-metric, 90-day scan unchanged.
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
/**
|
|
* v1.44 — the health-status changepoint scan over 180 days against a real
|
|
* Postgres. Past the DAY tier's 90-day reach the read folds per day in SQL
|
|
* (`readDayAggregates`); this proves that path finds a step, reports its
|
|
* segment sizes, and leaves a switched-off module's metric unread.
|
|
*/
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { getPrismaClient, truncateAllTables } from "./setup";
|
|
import { buildExtendedChangepointSignals } from "@/lib/insights/derived/changepoint";
|
|
|
|
vi.mock("@/lib/db-compat", () => ({
|
|
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
|
function at(daysAgo: number): Date {
|
|
const d = new Date(Date.now() - daysAgo * DAY_MS);
|
|
d.setUTCHours(12, 0, 0, 0);
|
|
return d;
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await truncateAllTables(getPrismaClient());
|
|
});
|
|
|
|
describe("extended changepoint scan — real per-day fold", () => {
|
|
it("finds a step in a metric past the five, over 120 days", async () => {
|
|
const prisma = getPrismaClient();
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
username: "cp-extended",
|
|
email: "cp-extended@example.test",
|
|
role: "USER",
|
|
timezone: "UTC",
|
|
},
|
|
});
|
|
const rows = [];
|
|
// 120 days ago to 61 days ago at 14, then 60 days at 17, two readings a day.
|
|
for (let d = 120; d >= 1; d -= 1) {
|
|
const value = d > 60 ? 14 : 17;
|
|
rows.push(
|
|
{ value, measuredAt: at(d) },
|
|
{ value, measuredAt: new Date(at(d).getTime() + 3_600_000) },
|
|
);
|
|
rows.push({
|
|
value: d > 60 ? 140 : 150,
|
|
measuredAt: at(d),
|
|
type: "BLOOD_GLUCOSE" as const,
|
|
unit: "mg/dL",
|
|
});
|
|
}
|
|
await prisma.measurement.createMany({
|
|
data: rows.map((r) => ({
|
|
userId: user.id,
|
|
type: "RESPIRATORY_RATE" as const,
|
|
unit: "count/min",
|
|
source: "APPLE_HEALTH" as const,
|
|
...r,
|
|
})),
|
|
});
|
|
|
|
const shifts = await buildExtendedChangepointSignals(user.id, new Date(), {
|
|
glucose: false,
|
|
});
|
|
expect(shifts.map((s) => s.metric)).toEqual(["RESPIRATORY_RATE"]);
|
|
expect(shifts[0]).toMatchObject({
|
|
beforeMean: 14,
|
|
afterMean: 17,
|
|
direction: "up",
|
|
beforeDays: 60,
|
|
afterDays: 60,
|
|
});
|
|
});
|
|
});
|