diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index d1cc83304..d8408a6c6 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -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: diff --git a/messages/de.json b/messages/de.json index 5daba7282..f140521eb 100644 --- a/messages/de.json +++ b/messages/de.json @@ -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", diff --git a/src/components/medications/glp1-medication-card.tsx b/src/components/medications/glp1-medication-card.tsx index acc49476e..6a3ee1b60 100644 --- a/src/components/medications/glp1-medication-card.tsx +++ b/src/components/medications/glp1-medication-card.tsx @@ -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(null); const [showHistory, setShowHistory] = useState(false); diff --git a/src/lib/ai/coach/glp1-snapshot.ts b/src/lib/ai/coach/glp1-snapshot.ts index 84278726d..8b4428151 100644 --- a/src/lib/ai/coach/glp1-snapshot.ts +++ b/src/lib/ai/coach/glp1-snapshot.ts @@ -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 { + // 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, diff --git a/src/lib/insights/glp1-plateau.ts b/src/lib/insights/glp1-plateau.ts index 501ee5b78..431d0c2d6 100644 --- a/src/lib/insights/glp1-plateau.ts +++ b/src/lib/insights/glp1-plateau.ts @@ -41,6 +41,11 @@ export async function detectGlp1Plateau( userId: string, now: Date = new Date(), ): Promise { + // 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: {