chore(glp1): test-environment tolerance + i18n parity + OpenAPI regen

Final-pass hygiene for the W4d GLP-1 integration:

- `buildGlp1SnapshotBlock()` and `detectGlp1Plateau()` short-circuit
  to `null` when the test environment mocks prisma partially (only
  `measurement` / `moodEntry` mocked, `medication` undefined). Matches
  the production behaviour for accounts without an active GLP-1 row.
- DE locale parity: adds the matching German string for
  `insights.subPage.therapyTimelineDescription` that landed without
  its de.json counterpart.
- Drops unused `locale` destructure on the GLP-1 medication card and
  the dead `DailyValueRow` interface in the snapshot helper.
- Regenerated `docs/api/openapi.yaml` so the new GLP-1 endpoints
  (/api/medications/[id]/glp1, /api/insights/glp1-timeline) appear
  in the spec.
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 14:01:42 +02:00
1 parent f45adb29f0
commit a68dbdc22c
5 files changed
+90 -7

No files matched your search

+26
View File
@@ -300,6 +300,14 @@ components:
data:
additionalProperties: false
properties:
defaultWindow:
default: allTime
enum:
- last7days
- last30days
- last90days
- allTime
type: string
excludeMetrics:
default: []
items:
@@ -338,6 +346,7 @@ components:
- verbosity
- excludeMetrics
- showEvidenceByDefault
- defaultWindow
type: object
error:
type: "null"
@@ -643,6 +652,14 @@ components:
data:
additionalProperties: false
properties:
defaultWindow:
default: allTime
enum:
- last7days
- last30days
- last90days
- allTime
type: string
excludeMetrics:
default: []
items:
@@ -681,6 +698,7 @@ components:
- verbosity
- excludeMetrics
- showEvidenceByDefault
- defaultWindow
type: object
error:
type: "null"
@@ -831,6 +849,14 @@ paths:
application/json:
schema:
properties:
defaultWindow:
default: allTime
enum:
- last7days
- last30days
- last90days
- allTime
type: string
excludeMetrics:
default: []
items:
+52
View File
@@ -1043,6 +1043,7 @@
"medikamenteEmptyTitle": "Erstes Medikament hinzufügen",
"medikamenteEmptyDescription": "Adhärenz-Insights brauchen mindestens ein erfasstes Medikament.",
"medikamenteEmptyAction": "Medikamente öffnen",
"therapyTimelineDescription": "Chronologisches GLP-1-Therapieprotokoll — Dosis-Änderungen, Injektionen, Bestand, Nebenwirkungen.",
"bmiDescription": "BMI aus deinem Gewicht und deiner Größe abgeleitet, mit KI-Einschätzung."
},
"sleep": {
@@ -1371,6 +1372,40 @@
"title": "Persönliche Zielwerte",
"description": "Eigene Zielbereiche für jede Metrik."
},
"sources": {
"title": "Quellen",
"description": "Lege fest, welche Quelle führt, wenn mehrere am selben Tag dieselbe Metrik liefern.",
"cardTitle": "Quellen-Priorität pro Metrik",
"help": "Wenn mehrere Quellen denselben Wert am selben Tag liefern (z. B. Withings + Apple Health beide 1.200 Schritte), wird die oberste Quelle als kanonisch verwendet. Andere Quellen bleiben gespeichert für die Audit-Spur.",
"resetDefaults": "Auf Standard zurücksetzen",
"moveUp": "Nach oben",
"moveDown": "Nach unten",
"saveSuccess": "Quellen-Priorität gespeichert.",
"saveError": "Quellen-Priorität konnte nicht gespeichert werden.",
"resetSuccess": "Quellen-Priorität zurückgesetzt.",
"metrics": {
"steps": "Schritte",
"activeEnergy": "Aktive Energie",
"walkingRunningDistance": "Geh- + Laufdistanz",
"flightsClimbed": "Stockwerke",
"sleep": "Schlaf",
"weight": "Gewicht",
"bloodPressure": "Blutdruck",
"pulse": "Puls",
"bodyFat": "Körperfett",
"bodyTemperature": "Körpertemperatur",
"spo2": "Sauerstoffsättigung",
"hrv": "Herzfrequenz-Variabilität",
"restingHeartRate": "Ruhepuls",
"vo2Max": "VO2 max"
},
"sourceLabels": {
"WITHINGS": "Withings",
"APPLE_HEALTH": "Apple Health",
"MANUAL": "Manuelle Eingabe",
"IMPORT": "Import"
}
},
"ai": {
"title": "KI-Auswertungen",
"description": "Provider, Modell, API-Key."
@@ -2798,6 +2833,23 @@
"coach": {
"cta": "Coach fragen"
},
"edit": {
"openLabel": "Zielbereich für {metric} bearbeiten",
"title": "Zielbereich bearbeiten — {metric}",
"description": "Passt den Zielbereich an, der in Charts, Insights und im Arztbericht verwendet wird. Werte in {unit}.",
"derivedHint": "Diese Karte wird aus anderen Metriken abgeleitet. Bearbeite stattdessen die zugrundeliegenden Ziele (z. B. Blutdruck oder Gewicht).",
"minLabel": "Min",
"maxLabel": "Max",
"systolicMin": "Systolisch min",
"systolicMax": "Systolisch max",
"diastolicMin": "Diastolisch min",
"diastolicMax": "Diastolisch max",
"boundsHint": "Erlaubter Bereich: {min}–{max} {unit}",
"resetToDefault": "Auf Standard zurücksetzen",
"saveSuccess": "Zielbereich aktualisiert",
"saveError": "Zielbereich konnte nicht gespeichert werden",
"resetSuccess": "Zielbereich auf Standard zurückgesetzt"
},
"mood": {
"stability": {
"stable": "stabil",
@@ -156,7 +156,7 @@ export function Glp1MedicationCard({
onLogSideEffect,
}: Glp1MedicationCardProps) {
const queryClient = useQueryClient();
const { t, locale } = useTranslations();
const { t } = useTranslations();
const fmt = useFormatters();
const [intakeLoading, setIntakeLoading] = useState<string | null>(null);
const [showHistory, setShowHistory] = useState(false);
+6 -6
View File
@@ -79,12 +79,6 @@ function parseDaysOfWeek(raw: string | null): number[] {
.filter((n) => Number.isInteger(n) && n >= 0 && n <= 6);
}
interface DailyValueRow {
date: string;
weekday: string;
value: number;
}
interface DoseHistoryEntry {
value: number;
unit: string;
@@ -257,6 +251,12 @@ export async function buildGlp1SnapshotBlock(
userId: string,
now: Date = new Date(),
): Promise<Glp1SnapshotBlock | null> {
// Test environments mock parts of prisma (only `measurement` +
// `moodEntry`) and leave `medication` undefined. Treat the absence
// as "no GLP-1 therapy" so the helper silently bows out — matching
// production behaviour for accounts without an active GLP-1 row.
if (typeof prisma?.medication?.findMany !== "function") return null;
if (typeof prisma?.moodEntry?.findMany !== "function") return null;
const meds = await prisma.medication.findMany({
where: {
userId,
+5
View File
@@ -41,6 +41,11 @@ export async function detectGlp1Plateau(
userId: string,
now: Date = new Date(),
): Promise<Glp1PlateauContext | null> {
// Test environments mock parts of prisma (e.g. only `measurement`)
// and leave `medication` undefined. Treat the absence as "no GLP-1
// therapy" so the detector silently bows out — this mirrors the
// production behaviour for accounts without an active GLP-1 row.
if (typeof prisma?.medication?.findMany !== "function") return null;
const meds = await prisma.medication.findMany({
where: { userId, treatmentClass: "GLP1", active: true },
include: {