From 2861075676c3d554b3bfb22e655de5e085a5c6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 20 Oct 2026 04:37:15 +0200 Subject: [PATCH] fix(mood): bound the mood series fallback to the rollup window Without mood rollups the series walked every mood entry the account ever logged, while the rollup path reads the trailing five years. The live fallback now reads the same window. --- src/lib/analytics/__tests__/mood-series.test.ts | 13 +++++++++++++ src/lib/analytics/mood-series.ts | 7 ++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/analytics/__tests__/mood-series.test.ts b/src/lib/analytics/__tests__/mood-series.test.ts index 07ec24220..e29667876 100644 --- a/src/lib/analytics/__tests__/mood-series.test.ts +++ b/src/lib/analytics/__tests__/mood-series.test.ts @@ -83,6 +83,19 @@ describe("buildMoodDailySeries", () => { expect(series.summary.count).toBe(2); }); + it("bounds the live walk to the window the rollup read covers", async () => { + await buildMoodDailySeries("user-1"); + const [rollupUser, since] = vi.mocked(readMoodDayRollups).mock.calls[0] as [ + string, + Date, + ]; + expect(rollupUser).toBe("user-1"); + const args = vi.mocked(prisma.moodEntry.findMany).mock.calls[0]?.[0] as { + where: { moodLoggedAt?: { gte: Date } }; + }; + expect(args.where.moodLoggedAt?.gte).toEqual(since); + }); + it("returns an empty series for a user with no rollups and no raw entries", async () => { const series = await buildMoodDailySeries("user-1"); expect(series.entries).toEqual([]); diff --git a/src/lib/analytics/mood-series.ts b/src/lib/analytics/mood-series.ts index 676eb62e4..8e57d64e3 100644 --- a/src/lib/analytics/mood-series.ts +++ b/src/lib/analytics/mood-series.ts @@ -116,11 +116,12 @@ export async function buildMoodDailySeries( return { entries, summary, entryCount, source: "rollup" }; } - // Coverage fallback — no rollup rows yet. Run the legacy live walk ONCE; - // the warm-up above mints the rollups for the next request. + // Coverage fallback — no rollup rows yet. Run the live walk ONCE over the + // same window the rollup read covers; the warm-up above mints the rollups + // for the next request. const moodEntries = await client.moodEntry.findMany({ // v1.7.0 sync — exclude tombstoned rows from the fallback. - where: { userId, deletedAt: null }, + where: { userId, deletedAt: null, moodLoggedAt: { gte: since } }, orderBy: { moodLoggedAt: "asc" }, select: { date: true, score: true }, });