mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
feat(rollups): collapse overlapping sources across all read paths
Complete the source-aware rollup read path: the slim summaries slice (rollup + live), the comprehensive aggregator (both paths, including the dailyByType correlation feed), and the /api/measurements route (rollup read + the live date_trunc fallback) now resolve overlapping sources to the source-priority ladder's canonical reading per (type, day) before aggregating. A dual-source vital (e.g. WHOOP + Apple Watch resting heart rate) surfaces one canonical series instead of a blend or a doubled count; cumulative metrics keep summing per canonical source. The collapse runs in SQL where the aggregate must stay a single round-trip (buildSourceRankCase emits a per-user ladder-rank CASE; canonicalMeasurementsFrom restricts a live read to canonical-source rows) and in application code (collapseRollupRowsBySource) for the row readers, both driven by the same resolved ladders so live and rollup agree. Also widens the enum-literal SQL whitelist to accept digits so VO2_MAX no longer throws on the typed write-hook recompute or the rank CASE. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d06eec6ace
commit
7c494b60cb
14 files changed
+1248
-352
No files matched your search
@@ -44,7 +44,12 @@ vi.mock("@/lib/db", () => ({
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
deleteMany: vi.fn().mockResolvedValue({ count: 0 }),
|
||||
upsert: vi.fn().mockResolvedValue({}),
|
||||
// v1.11.1 — the writer now delete-then-inserts via createMany.
|
||||
createMany: vi.fn().mockResolvedValue({ count: 0 }),
|
||||
},
|
||||
// v1.11.1 — the source-aware rollup readers load the user's
|
||||
// source-priority blob; null → default ladders.
|
||||
user: { findUnique: vi.fn().mockResolvedValue(null) },
|
||||
$transaction: vi.fn().mockImplementation(async (queries: unknown[]) => {
|
||||
if (Array.isArray(queries)) return Promise.all(queries);
|
||||
return undefined;
|
||||
@@ -256,8 +261,13 @@ describe("GET /api/analytics", () => {
|
||||
// two parallel ones (all-time + windowed), so the cold fixture
|
||||
// now mocks 4 `$queryRaw` calls: coverage + allTime + windowed +
|
||||
// latest.
|
||||
vi.mocked(prisma.$queryRaw)
|
||||
.mockResolvedValueOnce([{ n: BigInt(0) }] as never)
|
||||
// The coverage probe rides `$queryRaw` (n:0 → cold path). v1.11.1 — the
|
||||
// cold path's data aggregates (allTime + windowed + latest) now run via
|
||||
// `$queryRawUnsafe` (the source-aware collapse splices a whitelisted CASE).
|
||||
vi.mocked(prisma.$queryRaw).mockResolvedValueOnce([
|
||||
{ n: BigInt(0) },
|
||||
] as never);
|
||||
vi.mocked(prisma.$queryRawUnsafe as any)
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
type: "WEIGHT",
|
||||
|
||||
Reference in new issue
Block a user