refactor(rollups): move measurement-rollups under src/lib/rollups umbrella

Creates the new src/lib/rollups/ aggregator directory and relocates
the main measurement rollup module:

- src/lib/measurements/rollups.ts → src/lib/rollups/measurement-rollups.ts
- src/lib/measurements/__tests__/rollups.test.ts →
  src/lib/rollups/__tests__/measurement-rollups.test.ts

Every importer of @/lib/measurements/rollups is rewritten to
@/lib/rollups/measurement-rollups. The orphan sibling reference inside
src/lib/measurements/rollup-read.ts (still in its old home until the
next batch) is promoted to the @/lib alias so the chain keeps
typechecking through the in-flight move.

Pure relocation — no behaviour change, no exported-surface change. The
umbrella exists so the rollup contract is discoverable from a single
directory before the iOS sprint locks the boundary.
This commit is contained in:
Marc-André Bombeck committed 2026-05-21 12:19:45 +02:00
1 parent 8974e7730c
commit 29bfcc674b
34 files changed
+587 -42

No files matched your search

@@ -76,7 +76,7 @@ vi.mock("@/lib/logging/context", async () => {
// The three fast-paths and the rollup helpers are exercised by their
// own tests; here we stub them so the route's per-type read is the
// only Prisma traffic the test observes.
vi.mock("@/lib/measurements/rollups", () => ({
vi.mock("@/lib/rollups/measurement-rollups", () => ({
ensureUserRollupsFresh: vi.fn().mockResolvedValue(undefined),
}));
@@ -150,7 +150,7 @@ beforeEach(async () => {
// module scope only sets the initial implementation, and the slim
// slice test must observe a resolved value not `undefined`.
const { ensureUserRollupsFresh } = await import(
"@/lib/measurements/rollups"
"@/lib/rollups/measurement-rollups"
);
const { probeRollupCoverage } = await import(
"@/lib/measurements/rollup-coverage"
@@ -48,7 +48,7 @@ import {
recomputeUserMedicationCompliance,
MEDICATION_COMPLIANCE_BACKFILL_DAYS,
} from "@/lib/medications/compliance-rollups";
import { recomputeUserRollups } from "@/lib/measurements/rollups";
import { recomputeUserRollups } from "@/lib/rollups/measurement-rollups";
export const dynamic = "force-dynamic";
+1 -1
View File
@@ -38,7 +38,7 @@ import { annotate } from "@/lib/logging/context";
import {
enqueueBootTimeRollupBackfill,
recomputeUserRollups,
} from "@/lib/measurements/rollups";
} from "@/lib/rollups/measurement-rollups";
interface RecomputeBody {
userId?: string;
+1 -1
View File
@@ -15,7 +15,7 @@ import type {
} from "@/generated/prisma/client";
import { measurementTypeEnum } from "@/lib/validations/measurement";
import { pickCanonicalSourceRows } from "@/lib/analytics/source-priority";
import { ensureUserRollupsFresh } from "@/lib/measurements/rollups";
import { ensureUserRollupsFresh } from "@/lib/rollups/measurement-rollups";
import { probeRollupCoverage } from "@/lib/measurements/rollup-coverage";
import { computeBpInTargetFastPath } from "@/lib/analytics/bp-in-target-fast-path";
import { computeUserHealthScoreFastPath } from "@/lib/analytics/health-score-fast-path";
+4 -4
View File
@@ -31,10 +31,10 @@ vi.mock("@/lib/logging/context", () => ({
// assert that the import path now folds the persistent rollup tier
// for each touched (type, day). Mood rollup mock stays no-op because
// the existing v1.4.39 W-MOOD hook already covers the moodlog branch.
vi.mock("@/lib/measurements/rollups", async () => {
vi.mock("@/lib/rollups/measurement-rollups", async () => {
const actual = await vi.importActual<
typeof import("@/lib/measurements/rollups")
>("@/lib/measurements/rollups");
typeof import("@/lib/rollups/measurement-rollups")
>("@/lib/rollups/measurement-rollups");
return {
...actual,
recomputeBucketsForMeasurement: vi.fn().mockResolvedValue(undefined),
@@ -49,7 +49,7 @@ import { NextRequest } from "next/server";
import { POST } from "../route";
import { checkRateLimit } from "@/lib/rate-limit";
import { prisma } from "@/lib/db";
import { recomputeBucketsForMeasurement } from "@/lib/measurements/rollups";
import { recomputeBucketsForMeasurement } from "@/lib/rollups/measurement-rollups";
beforeEach(() => {
vi.resetAllMocks();
+1 -1
View File
@@ -21,7 +21,7 @@ import { recomputeUserMoodRollups } from "@/lib/mood/rollups";
import {
collapseToTypeDayKeys,
recomputeBucketsForMeasurement,
} from "@/lib/measurements/rollups";
} from "@/lib/rollups/measurement-rollups";
import type { MeasurementType } from "@/generated/prisma/client";
// Derived from canonical enum so round-trip export → import covers every
+1 -1
View File
@@ -10,7 +10,7 @@ import {
} from "@/lib/api-response";
import { updateMeasurementSchema } from "@/lib/validations/measurement";
import { invalidateUserMeasurements } from "@/lib/cache/invalidate";
import { recomputeBucketsForMeasurement } from "@/lib/measurements/rollups";
import { recomputeBucketsForMeasurement } from "@/lib/rollups/measurement-rollups";
import { Prisma } from "@/generated/prisma/client";
import { NextRequest } from "next/server";
@@ -30,10 +30,10 @@ vi.mock("@/lib/db", () => ({
// v1.4.39.2 — the rollup branch's coverage-mismatch fallback may call
// `recomputeUserRollups` inline. Mock it so tests can assert whether
// the inline fold fired without exercising the real populator.
vi.mock("@/lib/measurements/rollups", async () => {
vi.mock("@/lib/rollups/measurement-rollups", async () => {
const actual = await vi.importActual<
typeof import("@/lib/measurements/rollups")
>("@/lib/measurements/rollups");
typeof import("@/lib/rollups/measurement-rollups")
>("@/lib/rollups/measurement-rollups");
return {
...actual,
recomputeBucketsForMeasurement: vi.fn().mockResolvedValue(undefined),
@@ -61,7 +61,7 @@ vi.mock("next/headers", () => ({
import { GET } from "../route";
import { prisma } from "@/lib/db";
import { getSession } from "@/lib/auth/session";
import { recomputeUserRollups } from "@/lib/measurements/rollups";
import { recomputeUserRollups } from "@/lib/rollups/measurement-rollups";
const SESSION_OK = {
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
+1 -1
View File
@@ -41,7 +41,7 @@ import { invalidateUserMeasurements } from "@/lib/cache/invalidate";
import {
recomputeBucketsForMeasurement,
collapseToTypeDayKeys,
} from "@/lib/measurements/rollups";
} from "@/lib/rollups/measurement-rollups";
import { Prisma, type MeasurementType } from "@/generated/prisma/client";
// v1.4.25 W16c — historical-backfill threshold for PR push
@@ -35,7 +35,7 @@ import { invalidateUserMeasurements } from "@/lib/cache/invalidate";
import {
recomputeBucketsForMeasurement,
collapseToTypeDayKeys,
} from "@/lib/measurements/rollups";
} from "@/lib/rollups/measurement-rollups";
const MAX_BATCH_ENTRIES = 500;
+1 -1
View File
@@ -30,7 +30,7 @@ import {
recomputeBucketsForMeasurement,
collapseToTypeDayKeys,
recomputeUserRollups,
} from "@/lib/measurements/rollups";
} from "@/lib/rollups/measurement-rollups";
import { NextRequest } from "next/server";
import type {
MeasurementType,
+1 -1
View File
@@ -52,7 +52,7 @@
*/
import { prisma } from "@/lib/db";
import { annotate } from "@/lib/logging/context";
import { readRollupBuckets } from "@/lib/measurements/rollups";
import { readRollupBuckets } from "@/lib/rollups/measurement-rollups";
import {
probeRollupCoverage,
type RollupCoverageMap,
+1 -1
View File
@@ -45,7 +45,7 @@
*/
import { prisma } from "@/lib/db";
import { annotate } from "@/lib/logging/context";
import { readRollupBuckets } from "@/lib/measurements/rollups";
import { readRollupBuckets } from "@/lib/rollups/measurement-rollups";
import {
probeRollupCoverage,
type RollupCoverageMap,
+1 -1
View File
@@ -40,7 +40,7 @@
*/
import { prisma } from "@/lib/db";
import { annotate } from "@/lib/logging/context";
import { readRollupBuckets } from "@/lib/measurements/rollups";
import { readRollupBuckets } from "@/lib/rollups/measurement-rollups";
import {
probeRollupCoverage,
type RollupCoverageMap,
+1 -1
View File
@@ -59,7 +59,7 @@ import { prisma } from "@/lib/db";
import type { DataSummary } from "@/lib/analytics/trends";
import { measurementTypeEnum } from "@/lib/validations/measurement";
import { annotate } from "@/lib/logging/context";
import { ensureUserRollupsFresh } from "@/lib/measurements/rollups";
import { ensureUserRollupsFresh } from "@/lib/rollups/measurement-rollups";
import {
isFullyCovered,
probeRollupCoverage,
+1 -1
View File
@@ -100,7 +100,7 @@
import { prisma } from "@/lib/db";
import type { DataSummary } from "@/lib/analytics/trends";
import { annotate } from "@/lib/logging/context";
import { ensureUserRollupsFresh } from "@/lib/measurements/rollups";
import { ensureUserRollupsFresh } from "@/lib/rollups/measurement-rollups";
import { aggregateBuckets } from "@/lib/measurements/rollup-read";
import {
isFullyCovered,
+1 -1
View File
@@ -15,7 +15,7 @@ import {
type CorrelationResult,
} from "@/lib/analytics/correlations";
import { getMedicationCategories } from "@/lib/medication-category";
import { readRollupBuckets } from "@/lib/measurements/rollups";
import { readRollupBuckets } from "@/lib/rollups/measurement-rollups";
import {
ensureUserMoodRollupsFresh,
readMoodDayRollups,
+1 -1
View File
@@ -36,7 +36,7 @@ import {
type ImportJobProgress,
type ImportJobResult,
} from "@/lib/measurements/import-apple-health-export";
import { recomputeUserRollups } from "@/lib/measurements/rollups";
import { recomputeUserRollups } from "@/lib/rollups/measurement-rollups";
/** Queue name for the Apple Health export ingest. */
export const APPLE_HEALTH_IMPORT_QUEUE = "apple-health-import";
+1 -1
View File
@@ -66,7 +66,7 @@ import {
recomputeUserRollups,
type RollupFullBackfillPayload,
type RollupRecomputePayload,
} from "@/lib/measurements/rollups";
} from "@/lib/rollups/measurement-rollups";
import {
MOOD_ROLLUP_FULL_BACKFILL_QUEUE,
MOOD_ROLLUP_FULL_BACKFILL_CONCURRENCY,
+1 -1
View File
@@ -21,7 +21,7 @@
* the v1.4.34.1 / 4.5 aggregator survives.
*/
import type { MeasurementType } from "@/generated/prisma/client";
import { readRollupBuckets } from "./rollups";
import { readRollupBuckets } from "@/lib/rollups/measurement-rollups";
export interface DailyMeanRow {
day: Date;
@@ -89,7 +89,7 @@ import {
enqueueRollupRecompute,
ensureUserRollupsFresh,
recomputeBucketsForMeasurement,
} from "../rollups";
} from "../measurement-rollups";
beforeEach(() => {
queryRaw.mockReset();
File renamed without changes.
@@ -49,10 +49,10 @@ vi.mock("@/lib/logging/context", () => ({
// measurement rollup writer so the dashboard chart's `source=rollup`
// fast-path sees the new buckets. The mock collects the call args so
// the regression test below can assert the hook fired.
vi.mock("@/lib/measurements/rollups", async () => {
vi.mock("@/lib/rollups/measurement-rollups", async () => {
const actual = await vi.importActual<
typeof import("@/lib/measurements/rollups")
>("@/lib/measurements/rollups");
typeof import("@/lib/rollups/measurement-rollups")
>("@/lib/rollups/measurement-rollups");
return {
...actual,
recomputeBucketsForMeasurement: vi.fn().mockResolvedValue(undefined),
@@ -65,7 +65,7 @@ import {
recordSyncFailure,
recordSyncSuccess,
} from "@/lib/integrations/status";
import { recomputeBucketsForMeasurement } from "@/lib/measurements/rollups";
import { recomputeBucketsForMeasurement } from "@/lib/rollups/measurement-rollups";
import { fetchWithingsActivity, syncUserActivity } from "../sync-activity";
@@ -48,10 +48,10 @@ vi.mock("@/lib/logging/context", () => ({
// rollup tier for each touched SLEEP_DURATION day so the dashboard
// chart's `source=rollup` fast-path sees the new segments. The mock
// surfaces the call args for the regression test below.
vi.mock("@/lib/measurements/rollups", async () => {
vi.mock("@/lib/rollups/measurement-rollups", async () => {
const actual = await vi.importActual<
typeof import("@/lib/measurements/rollups")
>("@/lib/measurements/rollups");
typeof import("@/lib/rollups/measurement-rollups")
>("@/lib/rollups/measurement-rollups");
return {
...actual,
recomputeBucketsForMeasurement: vi.fn().mockResolvedValue(undefined),
@@ -64,7 +64,7 @@ import {
recordSyncFailure,
recordSyncSuccess,
} from "@/lib/integrations/status";
import { recomputeBucketsForMeasurement } from "@/lib/measurements/rollups";
import { recomputeBucketsForMeasurement } from "@/lib/rollups/measurement-rollups";
import {
fetchWithingsSleep,
+1 -1
View File
@@ -41,7 +41,7 @@ import { getUnitForType } from "@/lib/validations/measurement";
import {
collapseToTypeDayKeys,
recomputeBucketsForMeasurement,
} from "@/lib/measurements/rollups";
} from "@/lib/rollups/measurement-rollups";
import { hasActivityScope } from "./client";
import {
+1 -1
View File
@@ -39,7 +39,7 @@ import { getUnitForType } from "@/lib/validations/measurement";
import {
collapseToTypeDayKeys,
recomputeBucketsForMeasurement,
} from "@/lib/measurements/rollups";
} from "@/lib/rollups/measurement-rollups";
import { hasActivityScope } from "./client";
import {
+1 -1
View File
@@ -21,7 +21,7 @@ import {
import {
collapseToTypeDayKeys,
recomputeBucketsForMeasurement,
} from "@/lib/measurements/rollups";
} from "@/lib/rollups/measurement-rollups";
/**
* Build the callback URL handed to Withings at `Notify.subscribe` time.