chore(release): v1.4.39 — mood, medication-compliance, and cumulative-sum rollup tiers

Squashed merge of the v1.4.39 wave work from develop. The release
extends the v1.4.38 fast-path posture to two more endpoints that
still walked the source table on every cold mount and folds a
cumulative `sum_value` column into the existing rollup tier so step /
flight / distance / daylight / active-energy sparklines no longer
re-derive their daily totals in Node.

Raw measurements / mood_entries / medication_intake_events tables are
byte-unchanged. Three new tiers (mood_entry_rollups,
medication_compliance_rollups, measurement_rollups.sum_value) are
derived caches that self-heal via boot-time backfill on first reach.

Migrations 0070, 0071, 0072 are additive only. The sum_value column
add is catalog-only DDL on PostgreSQL 11+ — no table rewrite, writes
resume in milliseconds. No environment-variable change. No API
contract break. No iOS contract change.

Expected wins (live perf-verify rides the post-deploy window):
- /api/mood/analytics cold 12.7 s -> ~200 ms
- /api/medications/intake?scope=compliance cold 3.2 s -> ~200 ms
- /api/dashboard/summary cumulative sparkline ~500 ms -> ~300 ms
- /api/analytics live-fallback row cap 347 k -> ~5 k (425 d cap
  preserves the year-ago baseline tile)

Tests 4 524 -> 4 640 (+116). Typecheck clean. Lint clean.

See CHANGELOG.md for the full v1.4.39 entry.
This commit is contained in:
Marc-André Bombeck committed 2026-05-21 07:39:10 +02:00
1 parent 8a8150d2bf
commit d84922cdc3
59 files changed
+8272 -29

No files matched your search

@@ -43,6 +43,11 @@ import {
summarizeBackup,
type BackupSummary,
} from "@/lib/validations/backup";
import { recomputeUserMoodRollups } from "@/lib/mood/rollups";
import {
recomputeUserMedicationCompliance,
MEDICATION_COMPLIANCE_BACKFILL_DAYS,
} from "@/lib/medications/compliance-rollups";
export const dynamic = "force-dynamic";
@@ -281,6 +286,11 @@ const handler = apiHandler(
const moods = await tx.moodEntry.deleteMany({
where: { userId: ownerId },
});
// v1.4.39 W-MOOD — wipe the persisted mood rollup partition
// for this owner so the next analytics read doesn't surface
// pre-restore daily means. The fold below mints fresh rows
// from the restored mood entries.
await tx.moodEntryRollup.deleteMany({ where: { userId: ownerId } });
const channels = await tx.notificationChannel.deleteMany({
where: { userId: ownerId },
});
@@ -410,6 +420,52 @@ const handler = apiHandler(
return apiError("Restore failed", 500);
}
// v1.4.39 W-MOOD — re-fold the mood rollup tier from the just-
// restored entries. Runs outside the transaction so the (5-year)
// fold can't hold a long write lock; best-effort so a populator
// hiccup doesn't undo the restore. The boot-time backfill is the
// safety net — if this fails the next worker boot mints the rows.
if (payload.moodEntries.length > 0) {
try {
await recomputeUserMoodRollups(ownerId, { granularities: ["DAY"] });
} catch (err) {
annotate({
meta: {
mood_rollup_restore_failed: true,
mood_rollup_restore_error:
err instanceof Error ? err.message : String(err),
},
});
}
}
// v1.4.39 W-MED — re-fold the medication-compliance rollup tier
// from the restored intake events. The medication delete inside the
// transaction already cascaded the existing rollup partition for
// this owner via the FK, so the fold mints fresh rows. Boot-time
// backfill is the safety net if this best-effort call fails.
if (payload.intakeEvents.length > 0) {
try {
const restoreUser = await prisma.user.findUnique({
where: { id: ownerId },
select: { timezone: true },
});
await recomputeUserMedicationCompliance(
ownerId,
MEDICATION_COMPLIANCE_BACKFILL_DAYS,
restoreUser?.timezone ?? null,
);
} catch (err) {
annotate({
meta: {
medication_compliance_rollup_restore_failed: true,
medication_compliance_rollup_restore_error:
err instanceof Error ? err.message : String(err),
},
});
}
}
const summary = summarizeBackup(payload);
await auditLog("admin.backups.restore", {