Files
HealthLog/src/lib/validations/user-prefs.ts
T
Marc-André Bombeck b045703493 feat(apple-health): ingest the activity rings, mindfulness, State of Mind and AFib burden
Six Apple Health signals leave the deferred list and ride
POST /api/measurements/batch and the export.zip import end to end:
EXERCISE_MINUTES, STAND_MINUTES and STAND_HOURS (the exercise and stand
rings), MINDFUL_MINUTES, STATE_OF_MIND_VALENCE (iOS 18 State of Mind,
-1..1) and AFIB_BURDEN (AFib History, percent of the week). The ring
types and mindful minutes are cumulative, so the daily rollup is their
sum; AFib burden is scaled strictly from Apple's fraction and a value
above 1 is refused rather than guessed at. The archive import takes
mindful minutes from the session's dates and skips idle stand hours.
Every registry carries them: units, ranges, labels in all locales,
categories, chart tokens, report catalogue (mindful minutes and the
valence fenced as sensitive), signal registry with FHIR codings, and
MCP exposure for the three ring types only.

Workout GPS tracks are now an opt-in, off by default
(PATCH /api/auth/me/workout-routes, a switch on Data & Privacy). While
it is off, the workout batch route and the export import keep every
workout and drop its track with the same per-entry status. With it on,
the import reads the referenced GPX files from the archive one bounded
member at a time, thins each to the point cap and stores it sealed.
The climb a track carries is written to Workout.elevationM at write
time, so the workout insight no longer opens the track, and the AI
egress guard now freezes the set of files that read route geometry.
2026-10-21 20:14:20 +02:00

66 lines
2.4 KiB
TypeScript

/**
* Request schemas for the per-user scalar preference endpoints under
* `/api/auth/me/*`. Single-sourced here so the runtime `safeParse` and the
* OpenAPI registry (`src/lib/openapi/routes/profile.ts`) describe the identical
* wire contract — the same reason `mfa.ts` exists next door.
*
* They all follow the same shape: a one-field PATCH that hard-sets the value
* and answers with the resolved next state, so a client can replace its
* optimistic update rather than re-read.
*/
import { z } from "zod/v4";
import {
injectionSiteEnum,
INJECTION_SITE_VALUES,
} from "@/lib/validations/medication";
/** `PATCH /api/auth/me/unit-preference` — metric/imperial display branch. */
export const unitPreferencePatchSchema = z.object({
unitPreference: z.enum(["metric", "imperial"]),
});
/**
* `PATCH /api/auth/me/glucose-unit` — mg/dL vs mmol/L display branch.
*
* Not the metric/imperial preference: the two split along different lines.
* Germany is metric and reads glucose in mg/dL; the UK and the Nordics read
* it in mmol/L. Canonical storage stays mg/dL either way.
*/
export const glucoseUnitPatchSchema = z.object({
glucoseUnit: z.enum(["mg/dL", "mmol/L"]),
});
/** `PATCH /api/auth/me/documents-auto-ai-read` — the auto-read opt-in. */
export const documentsAutoAiReadPatchSchema = z.object({
documentsAutoAiRead: z.boolean(),
});
/** `PATCH /api/auth/me/labs-local-ocr` — browser-side OCR opt-in. */
export const labsLocalOcrPatchSchema = z.object({
labsLocalOcrEnabled: z.boolean(),
});
/** `PATCH /api/auth/me/workout-routes` — keep workout GPS tracks (opt-in). */
export const workoutRoutesPatchSchema = z.object({
storeWorkoutRoutes: z.boolean(),
});
/** `PATCH /api/auth/me/use-central-codex` — the operator's shared Codex. */
export const useCentralCodexPatchSchema = z.object({
useCentralCodex: z.boolean(),
});
/** `PATCH /api/auth/me/injection-site-prefs` — the user-level deny-list. */
export const injectionSitePrefsPatchSchema = z.object({
// Dedup at write time; cap at the full enum size. An empty array
// clears the exclusion.
globalExcludedInjectionSites: z
.array(injectionSiteEnum)
.max(INJECTION_SITE_VALUES.length)
.meta({
description:
"User-level injection-site deny-list. Sites here are never offered for any medication and rejected at intake (deny wins). Empty array clears the exclusion.",
}),
});