fix(api): publish what the GET handlers send

A response parsed against its own registered schema disagreed in thirteen
operations. Where the handler was right, the schema now says so; where the
handler had drifted from its siblings, it follows them.

Schema:
- measurements list and detail declare aggregationProvenance
- series-batch and sleep night stages are partial records: only the
  requested types and the recorded stages are present
- medications declare deliveryForm, the injection-site fields, externalId,
  externalSource, the low-stock bookkeeping, schedules[].doseWindows and
  nextDueScheduleId on the list
- notification prefs declare coach.nudgeAiComposed and measurementReminder
- mood insights correlations declare suppressed
- record settings profile.unitPreference is nullable, as stored
- insights/comprehensive describes the computed overview it returns rather
  than the model digest fields it never sends

Handler:
- GET /api/mood-entries/{id} shares the list projection and now carries
  tagKeys and ratedFactors
- GET and PATCH /api/custom-metrics/{id} carry latest and entryCount
- /api/analytics no longer leaks anomalyCount through the per-night sleep
  summary and the glucose meal-context split
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 13:16:22 +02:00
1 parent 67cc56f707
commit aecf6f91b1
18 files changed
+1214 -283

No files matched your search

+5 -3
View File
@@ -5,7 +5,7 @@ import { apiSuccess, returnAllZodIssues } from "@/lib/api-response";
import { analyticsQuerySchema } from "@/lib/validations/analytics";
import { NO_STORE_BUT_BFCACHE } from "@/lib/http/cache-headers";
import { cachedSwr, caches, type ServerCache } from "@/lib/cache/server-cache";
import { summarize, type DataPoint } from "@/lib/analytics/trends";
import { summarize, wireSummary, type DataPoint } from "@/lib/analytics/trends";
import { readSummariesSlice } from "@/lib/analytics/summaries-slice";
import { loadRecoveryEmpty } from "@/lib/insights/recovery-empty-read";
import { resolveEffectiveBpTargets } from "@/lib/analytics/effective-range";
@@ -500,8 +500,10 @@ async function buildAnalyticsResponse(user: AuthedUser, locale: Locale) {
glucoseRows,
(r) => r.glucoseContext,
)) {
glucoseByContext[bucket] = summarize(
ctxRows.map((r): DataPoint => ({ date: r.measuredAt, value: r.value })),
glucoseByContext[bucket] = wireSummary(
summarize(
ctxRows.map((r): DataPoint => ({ date: r.measuredAt, value: r.value })),
),
);
}