mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
Merge the persisted score record into the composition swap
This commit is contained in:
commit
0826bd80e6
18 files changed
+1614
-7
No files matched your search
@@ -257,6 +257,29 @@ function sourceClient() {
|
||||
},
|
||||
]),
|
||||
},
|
||||
// The score as it was SHOWN on a local day. Not recomputable: today's
|
||||
// number always comes from today's rows, so once the readings behind this
|
||||
// day moved, nothing can reproduce what it said.
|
||||
healthScoreRecord: {
|
||||
findMany: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: "score-day-1",
|
||||
userId: OWNER,
|
||||
dayKey: "2026-07-18",
|
||||
timezone: "Europe/Berlin",
|
||||
composite: 76,
|
||||
band: "yellow",
|
||||
scoreVersion: 2,
|
||||
composition: ["BLOOD_PRESSURE", "SLEEP", "ADIPOSITY"],
|
||||
pillarScores: { BLOOD_PRESSURE: 86, SLEEP: 100, ADIPOSITY: 42 },
|
||||
inputFingerprint: "a".repeat(64),
|
||||
configVersion: null,
|
||||
configChangedAt: null,
|
||||
computedAt: new Date("2026-07-18T20:00:00.000Z"),
|
||||
createdAt: new Date("2026-07-18T20:00:00.000Z"),
|
||||
},
|
||||
]),
|
||||
},
|
||||
customMetric: {
|
||||
findMany: vi.fn().mockResolvedValue([
|
||||
{
|
||||
@@ -586,6 +609,44 @@ describe("backup round trip — export, wire schema, restore", () => {
|
||||
expect(rows[0].hourlyCumulative).toEqual(DAY_CURVE);
|
||||
});
|
||||
|
||||
it("writes the recorded score day back with the number it showed", async () => {
|
||||
const { written } = await roundTrip();
|
||||
|
||||
const write = written.find(
|
||||
(w) => w.model === "healthScoreRecord" && w.op === "createMany",
|
||||
);
|
||||
expect(
|
||||
write,
|
||||
"the recorded score day reached the file and nothing wrote it back",
|
||||
).toBeDefined();
|
||||
|
||||
const rows = write!.data as unknown as Array<Record<string, unknown>>;
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0]).toMatchObject({
|
||||
id: "score-day-1",
|
||||
userId: OWNER,
|
||||
dayKey: "2026-07-18",
|
||||
timezone: "Europe/Berlin",
|
||||
composite: 76,
|
||||
band: "yellow",
|
||||
scoreVersion: 2,
|
||||
inputFingerprint: "a".repeat(64),
|
||||
});
|
||||
// The composition and the per-pillar scores are what keep the stored day
|
||||
// readable across a later change to what counts. A row that came back
|
||||
// with only its headline would look restored and answer nothing.
|
||||
expect(rows[0].composition).toEqual([
|
||||
"BLOOD_PRESSURE",
|
||||
"SLEEP",
|
||||
"ADIPOSITY",
|
||||
]);
|
||||
expect(rows[0].pillarScores).toEqual({
|
||||
BLOOD_PRESSURE: 86,
|
||||
SLEEP: 100,
|
||||
ADIPOSITY: 42,
|
||||
});
|
||||
});
|
||||
|
||||
it("writes the account's own cycle symptom back before resolving its links", async () => {
|
||||
const { written, res } = await roundTrip();
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import { recomputeUserRollups } from "@/lib/rollups/measurement-rollups";
|
||||
import { restoreCycleData } from "@/lib/cycle/backup";
|
||||
import { restoreProfileData } from "@/lib/export/profile-backup";
|
||||
import { restoreIntradayProfileData } from "@/lib/export/intraday-profile-backup";
|
||||
import { restoreHealthScoreData } from "@/lib/export/health-score-backup";
|
||||
import { invalidateUserData } from "@/lib/cache/invalidate";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -71,6 +72,7 @@ interface RestoreResponse {
|
||||
customMetrics: number;
|
||||
correlationPatterns: number;
|
||||
intradayProfiles: number;
|
||||
healthScoreRecords: number;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -764,6 +766,14 @@ const handler = apiHandler(
|
||||
payload,
|
||||
);
|
||||
|
||||
// The score as it was shown on each local day. Both ends live in
|
||||
// `src/lib/export/health-score-backup.ts`, same as the two above.
|
||||
const healthScoreCleared = await restoreHealthScoreData(
|
||||
tx,
|
||||
ownerId,
|
||||
payload,
|
||||
);
|
||||
|
||||
const biomarkerByName = new Map<string, string>();
|
||||
const restoredBiomarkerIds = new Set<string>();
|
||||
for (const biomarker of payload.biomarkers) {
|
||||
@@ -1128,6 +1138,7 @@ const handler = apiHandler(
|
||||
customMetrics: profileCleared.customMetrics,
|
||||
correlationPatterns: profileCleared.correlationPatterns,
|
||||
intradayProfiles: intradayCleared.intradayProfiles,
|
||||
healthScoreRecords: healthScoreCleared.healthScoreRecords,
|
||||
};
|
||||
},
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
computeBpInTargetFastPath,
|
||||
type BpInTargetEnvelope,
|
||||
} from "@/lib/analytics/bp-in-target-fast-path";
|
||||
import { computeUserHealthScore } from "@/lib/analytics/score/reader";
|
||||
import { computeAndRecordUserHealthScore } from "@/lib/analytics/score/record";
|
||||
import { isModuleEnabled } from "@/lib/modules/gate";
|
||||
import { resolveRestMode } from "@/lib/illness/rest-mode";
|
||||
import { deriveBpWindow90 } from "@/lib/analytics/window-confidence";
|
||||
@@ -554,7 +554,7 @@ async function buildAnalyticsResponse(user: AuthedUser, locale: Locale) {
|
||||
const glucoseByContextOut = glucoseEnabled ? glucoseByContext : null;
|
||||
const glucoseClinicalOut = glucoseEnabled ? glucoseClinical : null;
|
||||
const scoredAt = new Date();
|
||||
const healthScore = await computeUserHealthScore({
|
||||
const healthScore = await computeAndRecordUserHealthScore({
|
||||
userId: user.id,
|
||||
now: scoredAt,
|
||||
profile: {
|
||||
|
||||
@@ -27,6 +27,7 @@ vi.mock("@/lib/db", () => ({
|
||||
// The hourly shape of a cumulative day, read by
|
||||
// `buildIntradayProfileBackupSection`.
|
||||
intradayCumulativeProfile: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
healthScoreRecord: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
// v1.15.0 — cycle tables read by the full-backup helper.
|
||||
cycleProfile: { findUnique: vi.fn() },
|
||||
menstrualCycle: { findMany: vi.fn() },
|
||||
@@ -96,6 +97,7 @@ beforeEach(() => {
|
||||
vi.mocked(prisma.intradayCumulativeProfile.findMany).mockResolvedValue(
|
||||
[] as never,
|
||||
);
|
||||
vi.mocked(prisma.healthScoreRecord.findMany).mockResolvedValue([] as never);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -25,6 +25,7 @@ vi.mock("@/lib/db", () => ({
|
||||
// The hourly shape of a cumulative day, read by
|
||||
// `buildIntradayProfileBackupSection`.
|
||||
intradayCumulativeProfile: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
healthScoreRecord: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
cycleProfile: { findUnique: vi.fn().mockResolvedValue(null) },
|
||||
menstrualCycle: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
cycleDayLog: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
|
||||
Reference in new issue
Block a user