diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index d11197593..8dbae33e7 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -2087,6 +2087,12 @@ paths: - SLEEP_SCORE - BODY_TEMPERATURE_DEVIATION - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT - in: query name: from schema: @@ -2276,6 +2282,12 @@ paths: - SLEEP_SCORE - BODY_TEMPERATURE_DEVIATION - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT value: type: number measuredAt: @@ -5770,6 +5782,12 @@ paths: - SLEEP_SCORE - BODY_TEMPERATURE_DEVIATION - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT description: "The measurement type to read (single metric — no fan-out). Closed enum: an unknown type 422s." required: true description: "The measurement type to read (single metric — no fan-out). Closed enum: an unknown type 422s." @@ -7992,6 +8010,95 @@ paths: "401": *a1 "422": *a3 "429": *a4 + /api/mental-health/assessments: + get: + tags: + - Mental health + summary: List the caller's screener history + description: Returns PHQ-9 / GAD-7 administrations (newest first). Totals + bands + flags only; raw item answers are + never returned. + parameters: + - in: query + name: instrument + schema: + type: string + enum: + - PHQ9 + - GAD7 + - in: query + name: limit + schema: + type: integer + minimum: 1 + maximum: 200 + - in: query + name: offset + schema: + type: integer + minimum: 0 + maximum: 9007199254740991 + responses: + "200": + description: Screener history. + content: + application/json: + schema: + $ref: "#/components/schemas/ListMentalHealthAssessmentsEnvelope" + "401": *a1 + "422": *a3 + "429": *a4 + post: + tags: + - Mental health + summary: Record a completed PHQ-9 / GAD-7 screener + description: Stores one administration (item answers encrypted) and writes the derived total as a PHQ9_SCORE / + GAD7_SCORE measurement. On a positive PHQ-9 item-9 the response carries the locale-aware crisis-resource set. + requestBody: + content: + application/json: + schema: + type: object + properties: + instrument: + type: string + enum: + - PHQ9 + - GAD7 + items: + minItems: 1 + maxItems: 12 + type: array + items: + type: integer + minimum: 0 + maximum: 3 + functionalDifficulty: + type: integer + minimum: 0 + maximum: 3 + takenAt: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$ + tz: + type: string + maxLength: 64 + locale: + type: string + maxLength: 16 + required: + - instrument + - items + responses: + "201": + description: Assessment recorded. + content: + application/json: + schema: + $ref: "#/components/schemas/CreateMentalHealthAssessmentEnvelope" + "401": *a1 + "422": *a3 + "429": *a4 /api/onboarding/disclaimer: post: tags: @@ -12353,6 +12460,12 @@ components: - SLEEP_SCORE - BODY_TEMPERATURE_DEVIATION - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT value: type: number unit: @@ -12669,6 +12782,12 @@ components: - SLEEP_SCORE - BODY_TEMPERATURE_DEVIATION - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT additionalProperties: type: array items: @@ -12746,6 +12865,12 @@ components: - SLEEP_SCORE - BODY_TEMPERATURE_DEVIATION - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT value: type: number description: Daily mean (spot metrics) or SUM (cumulative). @@ -12844,6 +12969,12 @@ components: - SLEEP_SCORE - BODY_TEMPERATURE_DEVIATION - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT description: Per-type daily series, keyed by MeasurementType. required: - series @@ -13077,6 +13208,12 @@ components: - SLEEP_SCORE - BODY_TEMPERATURE_DEVIATION - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT value: type: number unit: @@ -23363,6 +23500,137 @@ components: (median) recovery gap (null below the min-sample floor — withholds a thin claim), a recurrence-by-month tally, a per-type breakdown, and the dominant driving vital (`gapDriverType`, null when no gap is surfaced). Retrospective only." + ListMentalHealthAssessmentsEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/ListMentalHealthAssessmentsResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + ListMentalHealthAssessmentsResponse: + type: object + properties: + assessments: + type: array + items: + $ref: "#/components/schemas/MentalHealthAssessment" + required: + - assessments + additionalProperties: false + MentalHealthAssessment: + type: object + properties: + id: + type: string + instrument: + type: string + enum: + - PHQ9 + - GAD7 + locale: + type: string + version: + type: string + totalScore: + type: number + severityBand: + type: string + item9Flagged: + type: boolean + crisisShownAt: + anyOf: + - type: string + - type: "null" + takenAt: + type: string + createdAt: + type: string + required: + - id + - instrument + - locale + - version + - totalScore + - severityBand + - item9Flagged + - crisisShownAt + - takenAt + - createdAt + additionalProperties: false + description: One completed screener administration. `severityBand` is a descriptive label for the screen (never a + diagnosis). `item9Flagged` is true when the PHQ-9 self-harm item was answered > 0 — the client MUST show crisis + resources when it is. Raw per-item answers are intentionally absent. + CreateMentalHealthAssessmentEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/CreateMentalHealthAssessmentResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + CreateMentalHealthAssessmentResponse: + type: object + properties: + assessment: + $ref: "#/components/schemas/MentalHealthAssessment" + actionThreshold: + type: number + crisis: + anyOf: + - $ref: "#/components/schemas/CrisisResourceSet" + - type: "null" + required: + - assessment + - actionThreshold + - crisis + additionalProperties: false + CrisisResourceSet: + type: object + properties: + emergencyNumber: + type: string + resources: + type: array + items: + $ref: "#/components/schemas/CrisisResource" + required: + - emergencyNumber + - resources + additionalProperties: false + description: Locale-aware crisis-resource signposting, present only when item-9 is flagged. Names resolve via i18n on + the client; contacts are literal. + CrisisResource: + type: object + properties: + id: + type: string + contacts: + type: array + items: + type: string + required: + - id + - contacts + additionalProperties: false DisclaimerAckEnvelope: type: object properties: diff --git a/messages/de.json b/messages/de.json index 146bbed3a..7a75ad3a0 100644 --- a/messages/de.json +++ b/messages/de.json @@ -575,7 +575,13 @@ "bulkDeleteSuccess": "{count} Messungen gelöscht", "bulkDeleteError": "Fehler beim Löschen der ausgewählten Messungen", "bulkDeleteConfirmTitle": "{count} Messungen löschen?", - "bulkDeleteConfirmBody": "Die {count} ausgewählten Messungen werden endgültig gelöscht." + "bulkDeleteConfirmBody": "Die {count} ausgewählten Messungen werden endgültig gelöscht.", + "typePhq9Score": "PHQ-9-Wert", + "typeGad7Score": "GAD-7-Wert", + "typeGripStrength": "Griffstärke", + "typePainNrs": "Schmerz (0–10)", + "typeWaistCircumference": "Taillenumfang", + "typeWaistToHeight": "Taille-Größe-Verhältnis" }, "mood": { "title": "Stimmung", @@ -7090,8 +7096,16 @@ "hemoglobin": "Das sauerstofftransportierende Protein in den roten Blutkörperchen.", "hematocrit": "Der Anteil der roten Blutkörperchen am Blutvolumen.", "wbc": "Gesamtzahl der weißen Blutkörperchen, der zirkulierenden Abwehr des Immunsystems.", - "platelets": "Zellfragmente, die sich zusammenlagern und die Blutgerinnung bewirken." - } + "platelets": "Zellfragmente, die sich zusammenlagern und die Blutgerinnung bewirken.", + "apob": "Zählt atherogene Partikel direkt; oft ein besserer Risikomarker als LDL-C, wenn beide auseinandergehen.", + "lp-a": "Ein weitgehend vererbter, lebenslanger Risikofaktor — meist einmal gemessen. In molaren Einheiten (nmol/L) angeben.", + "fasting-insulin": "Ergänzt den Nüchtern-Blutzucker zur Abschätzung der Insulinresistenz (HOMA-IR).", + "omega-3-index": "Anteil von EPA+DHA in den roten Blutkörperchen; 8–12 % gelten als kardioprotektiver Bereich." + }, + "apob": "Apolipoprotein B (ApoB)", + "lp-a": "Lipoprotein(a)", + "fasting-insulin": "Nüchtern-Insulin", + "omega-3-index": "Omega-3-Index" }, "noteLoadFailed": "Notiz konnte nicht geladen werden; sie bleibt beim Speichern unverändert.", "showingLatestOf": "Die neuesten {shown} von {total} Messwerten werden angezeigt", @@ -7426,5 +7440,118 @@ "reload": "Neu laden", "dismiss": "Schließen" } + }, + "mentalHealth": { + "pageTitle": "Check-in zum seelischen Wohlbefinden", + "pageDescription": "Zwei validierte, freiwillige Selbsttests — PHQ-9 (Stimmung) und GAD-7 (Sorgen) — ergänzend zu deinem Stimmungstagebuch. Aussagekräftig ist der Verlauf über die Zeit, nicht ein einzelner Wert.", + "disclaimer": "Dies ist ein Screening-Selbsttest, keine Diagnose. Ein hoher oder niedriger Wert bedeutet nicht, dass eine Erkrankung vorliegt oder nicht. Nur eine qualifizierte Fachperson kann eine Diagnose stellen. Bei Sorgen sprich bitte mit deiner Ärztin oder deinem Arzt.", + "optInNote": "Freiwillig und privat: Deine einzelnen Antworten sind verschlüsselt, bleiben aus dem KI-Coach und aus Exporten heraus und werden nie geteilt.", + "attributionLabel": "Quelle", + "choosePrompt": "Check-in auswählen", + "start": "Starten", + "back": "Zurück", + "stem": "Wie oft fühltest du dich im Verlauf der letzten 2 Wochen durch die folgenden Beschwerden beeinträchtigt?", + "submit": "Ergebnis ansehen", + "submitting": "Wird gespeichert…", + "saved": "Gespeichert", + "error": "Etwas ist schiefgelaufen. Bitte versuche es erneut.", + "considerProfessional": "Dein Wert liegt in einem Bereich, in dem ein Gespräch mit einer Fachperson helfen kann. Das ist ein behutsamer Hinweis, keine Diagnose.", + "instrument": { + "phq9": "PHQ-9 — Stimmung", + "gad7": "GAD-7 — Sorgen" + }, + "instrumentSub": { + "phq9": "Depressions-Screening, 9 Fragen", + "gad7": "Angst-Screening, 7 Fragen" + }, + "options": { + "0": "Überhaupt nicht", + "1": "An einzelnen Tagen", + "2": "An mehr als der Hälfte der Tage", + "3": "Beinahe jeden Tag" + }, + "functionalTitle": "Wenn du eine oder mehrere Beschwerden angegeben hast: Wie sehr haben diese deine Arbeit, deinen Haushalt oder den Umgang mit anderen Menschen erschwert?", + "functional": { + "0": "Überhaupt nicht erschwert", + "1": "Etwas erschwert", + "2": "Sehr erschwert", + "3": "Extrem erschwert" + }, + "result": { + "title": "Dein Ergebnis", + "totalLabel": "Gesamtwert", + "bandLabel": "Bereich für dieses Screening", + "takeAnother": "Weiteren Check-in machen" + }, + "band": { + "PHQ9": { + "minimal": "Keine / minimal", + "mild": "Leicht", + "moderate": "Mittelgradig", + "modSevere": "Mittelschwer", + "severe": "Schwer" + }, + "GAD7": { + "minimal": "Keine / minimal", + "mild": "Leicht", + "moderate": "Mittelgradig", + "severe": "Schwer" + } + }, + "history": { + "title": "Dein Verlauf", + "empty": "Noch keine Check-ins. Dein Verlauf erscheint hier, sobald du einen abgeschlossen hast." + }, + "crisis": { + "title": "Du musst das nicht allein durchstehen", + "intro": "Du hast Gedanken angegeben, lieber tot zu sein oder dir etwas anzutun. Es gibt jetzt sofort Unterstützung — sich zu melden ist ein Zeichen von Stärke.", + "ifDanger": "Wenn du in unmittelbarer Gefahr bist, ruf jetzt {emergency} an.", + "resourcesTitle": "Menschen, die helfen können" + }, + "items": { + "phq9": { + "1": "Wenig Interesse oder Freude an Ihren Tätigkeiten", + "2": "Niedergeschlagenheit, Schwermut oder Hoffnungslosigkeit", + "3": "Schwierigkeiten, ein- oder durchzuschlafen, oder vermehrter Schlaf", + "4": "Müdigkeit oder Gefühl, keine Energie zu haben", + "5": "Verminderter Appetit oder übermäßiges Bedürfnis zu essen", + "6": "Schlechte Meinung von sich selbst; Gefühl, ein Versager zu sein oder die Familie enttäuscht zu haben", + "7": "Schwierigkeiten, sich auf etwas zu konzentrieren, z. B. beim Zeitunglesen oder Fernsehen", + "8": "Waren Ihre Bewegungen oder Ihre Sprache so verlangsamt, dass es auch anderen auffallen würde? Oder waren Sie im Gegenteil „zappelig“ oder ruhelos und hatten einen stärkeren Bewegungsdrang als sonst?", + "9": "Gedanken, dass Sie lieber tot wären oder sich Leid zufügen möchten" + }, + "gad7": { + "1": "Nervosität, Ängstlichkeit oder Anspannung", + "2": "Nicht in der Lage sein, Sorgen zu stoppen oder zu kontrollieren", + "3": "Übermäßige Sorgen bezüglich verschiedener Angelegenheiten", + "4": "Schwierigkeiten zu entspannen", + "5": "Rastlosigkeit, sodass Stillsitzen schwerfällt", + "6": "Schnelle Verärgerung oder Gereiztheit", + "7": "Gefühl der Angst, als würde etwas Schlimmes passieren" + } + }, + "crisisResource": { + "telefonSeelsorge": { + "name": "TelefonSeelsorge (rund um die Uhr, kostenlos, vertraulich)" + }, + "nummerGegenKummer": { + "name": "Nummer gegen Kummer (für junge Menschen)" + }, + "krisenchat": { + "name": "krisenchat (24/7-Chat, junge Menschen)" + }, + "findahelpline": { + "name": "Find a Helpline (weltweites Verzeichnis)" + }, + "euEmotionalSupport": { + "name": "Telefonseelsorge-Hotline" + }, + "lifeline988": { + "name": "988 Suicide & Crisis Lifeline" + }, + "crisisTextLine": { + "name": "Crisis Text Line" + } + } } } diff --git a/messages/en.json b/messages/en.json index 23ae4f2b7..a8fbcc2b5 100644 --- a/messages/en.json +++ b/messages/en.json @@ -575,7 +575,13 @@ "bulkDeleteSuccess": "{count} measurements deleted", "bulkDeleteError": "Error deleting the selected measurements", "bulkDeleteConfirmTitle": "Delete {count} measurements?", - "bulkDeleteConfirmBody": "The {count} selected measurements will be permanently deleted." + "bulkDeleteConfirmBody": "The {count} selected measurements will be permanently deleted.", + "typePhq9Score": "PHQ-9 score", + "typeGad7Score": "GAD-7 score", + "typeGripStrength": "Grip strength", + "typePainNrs": "Pain (0–10)", + "typeWaistCircumference": "Waist circumference", + "typeWaistToHeight": "Waist-to-height ratio" }, "mood": { "title": "Mood", @@ -7090,8 +7096,16 @@ "hemoglobin": "The oxygen-carrying protein in red blood cells.", "hematocrit": "The fraction of blood volume made up of red blood cells.", "wbc": "Total count of white blood cells, the immune system's circulating defence.", - "platelets": "Cell fragments that clump together to form blood clots." - } + "platelets": "Cell fragments that clump together to form blood clots.", + "apob": "Counts atherogenic particles directly; often a better risk marker than LDL-C when the two disagree.", + "lp-a": "A largely inherited, lifelong risk factor — usually measured once. Report molar units (nmol/L).", + "fasting-insulin": "Pairs with fasting glucose to estimate insulin resistance (HOMA-IR).", + "omega-3-index": "Red-blood-cell EPA+DHA share; 8–12% is the commonly cited cardioprotective range." + }, + "apob": "Apolipoprotein B (ApoB)", + "lp-a": "Lipoprotein(a)", + "fasting-insulin": "Fasting insulin", + "omega-3-index": "Omega-3 index" }, "noteLoadFailed": "Note could not be loaded; it will be kept unchanged when you save.", "showingLatestOf": "Showing the latest {shown} of {total} readings", @@ -7426,5 +7440,118 @@ "reload": "Reload", "dismiss": "Dismiss" } + }, + "mentalHealth": { + "pageTitle": "Mental wellbeing check-in", + "pageDescription": "Two validated, opt-in self-assessments — PHQ-9 (mood) and GAD-7 (worry) — that sit beside your mood tracking. The value is the trend over time, not any single number.", + "disclaimer": "This is a screening self-assessment, not a diagnosis. A high or low score does not mean you do or do not have a condition. Only a qualified health professional can diagnose. If you have concerns, please talk to your doctor.", + "optInNote": "Opt-in and private: your individual answers are encrypted, kept out of the AI Coach and exports, and never shared.", + "attributionLabel": "Attribution", + "choosePrompt": "Choose a check-in", + "start": "Start", + "back": "Back", + "stem": "Over the last 2 weeks, how often have you been bothered by the following problems?", + "submit": "See my result", + "submitting": "Saving…", + "saved": "Saved", + "error": "Something went wrong. Please try again.", + "considerProfessional": "Your score is in a range where talking to a health professional may help. This is a gentle suggestion, not a diagnosis.", + "instrument": { + "phq9": "PHQ-9 — mood", + "gad7": "GAD-7 — worry" + }, + "instrumentSub": { + "phq9": "Depression screen, 9 questions", + "gad7": "Anxiety screen, 7 questions" + }, + "options": { + "0": "Not at all", + "1": "Several days", + "2": "More than half the days", + "3": "Nearly every day" + }, + "functionalTitle": "If you checked off any problems, how difficult have these made it to do your work, take care of things at home, or get along with other people?", + "functional": { + "0": "Not difficult at all", + "1": "Somewhat difficult", + "2": "Very difficult", + "3": "Extremely difficult" + }, + "result": { + "title": "Your result", + "totalLabel": "Total score", + "bandLabel": "Range for this screen", + "takeAnother": "Take another check-in" + }, + "band": { + "PHQ9": { + "minimal": "None / minimal", + "mild": "Mild", + "moderate": "Moderate", + "modSevere": "Moderately severe", + "severe": "Severe" + }, + "GAD7": { + "minimal": "None / minimal", + "mild": "Mild", + "moderate": "Moderate", + "severe": "Severe" + } + }, + "history": { + "title": "Your history", + "empty": "No check-ins yet. Your trend will appear here once you complete one." + }, + "crisis": { + "title": "You don't have to face this alone", + "intro": "You indicated thoughts of being better off dead or of hurting yourself. Support is available right now, and reaching out is a sign of strength.", + "ifDanger": "If you are in immediate danger, call {emergency} now.", + "resourcesTitle": "People who can help" + }, + "items": { + "phq9": { + "1": "Little interest or pleasure in doing things", + "2": "Feeling down, depressed, or hopeless", + "3": "Trouble falling or staying asleep, or sleeping too much", + "4": "Feeling tired or having little energy", + "5": "Poor appetite or overeating", + "6": "Feeling bad about yourself — or that you are a failure or have let yourself or your family down", + "7": "Trouble concentrating on things, such as reading the newspaper or watching television", + "8": "Moving or speaking so slowly that other people could have noticed. Or the opposite — being so fidgety or restless that you have been moving around a lot more than usual", + "9": "Thoughts that you would be better off dead, or of hurting yourself in some way" + }, + "gad7": { + "1": "Feeling nervous, anxious, or on edge", + "2": "Not being able to stop or control worrying", + "3": "Worrying too much about different things", + "4": "Trouble relaxing", + "5": "Being so restless that it is hard to sit still", + "6": "Becoming easily annoyed or irritable", + "7": "Feeling afraid as if something awful might happen" + } + }, + "crisisResource": { + "telefonSeelsorge": { + "name": "TelefonSeelsorge (24/7, free, confidential)" + }, + "nummerGegenKummer": { + "name": "Nummer gegen Kummer (for young people)" + }, + "krisenchat": { + "name": "krisenchat (24/7 chat, young people)" + }, + "findahelpline": { + "name": "Find a helpline (worldwide directory)" + }, + "euEmotionalSupport": { + "name": "Emotional-support helpline" + }, + "lifeline988": { + "name": "988 Suicide & Crisis Lifeline" + }, + "crisisTextLine": { + "name": "Crisis Text Line" + } + } } } diff --git a/messages/es.json b/messages/es.json index e2f12b186..b42bba6dc 100644 --- a/messages/es.json +++ b/messages/es.json @@ -575,7 +575,13 @@ "bulkDeleteSuccess": "{count} mediciones eliminadas", "bulkDeleteError": "Error al eliminar las mediciones seleccionadas", "bulkDeleteConfirmTitle": "¿Eliminar {count} mediciones?", - "bulkDeleteConfirmBody": "Las {count} mediciones seleccionadas se eliminarán de forma permanente." + "bulkDeleteConfirmBody": "Las {count} mediciones seleccionadas se eliminarán de forma permanente.", + "typePhq9Score": "Puntuación PHQ-9", + "typeGad7Score": "Puntuación GAD-7", + "typeGripStrength": "Fuerza de agarre", + "typePainNrs": "Dolor (0–10)", + "typeWaistCircumference": "Perímetro de cintura", + "typeWaistToHeight": "Índice cintura-altura" }, "mood": { "title": "Estado de ánimo", @@ -7090,8 +7096,16 @@ "hemoglobin": "La proteína de los glóbulos rojos que transporta el oxígeno.", "hematocrit": "La fracción del volumen sanguíneo formada por glóbulos rojos.", "wbc": "Recuento total de glóbulos blancos, la defensa circulante del sistema inmunitario.", - "platelets": "Fragmentos celulares que se agregan para formar coágulos sanguíneos." - } + "platelets": "Fragmentos celulares que se agregan para formar coágulos sanguíneos.", + "apob": "Cuenta directamente las partículas aterogénicas; a menudo mejor marcador de riesgo que el LDL-C cuando discrepan.", + "lp-a": "Factor de riesgo en gran parte hereditario y de por vida — suele medirse una vez. Informa en unidades molares (nmol/L).", + "fasting-insulin": "Se combina con la glucosa en ayunas para estimar la resistencia a la insulina (HOMA-IR).", + "omega-3-index": "Proporción de EPA+DHA en glóbulos rojos; 8–12 % es el rango cardioprotector citado habitualmente." + }, + "apob": "Apolipoproteína B (ApoB)", + "lp-a": "Lipoproteína(a)", + "fasting-insulin": "Insulina en ayunas", + "omega-3-index": "Índice omega-3" }, "noteLoadFailed": "No se pudo cargar la nota; se mantendrá sin cambios al guardar.", "showingLatestOf": "Mostrando los {shown} resultados más recientes de {total}", @@ -7426,5 +7440,118 @@ "reload": "Recargar", "dismiss": "Descartar" } + }, + "mentalHealth": { + "pageTitle": "Revisión de bienestar mental", + "pageDescription": "Dos autoevaluaciones validadas y opcionales — PHQ-9 (estado de ánimo) y GAD-7 (preocupación) — junto a tu registro de ánimo. Lo valioso es la tendencia en el tiempo, no un único número.", + "disclaimer": "Esto es una autoevaluación de cribado, no un diagnóstico. Una puntuación alta o baja no significa que tengas o no una afección. Solo un profesional de salud cualificado puede diagnosticar. Si tienes dudas, habla con tu médico.", + "optInNote": "Opcional y privado: tus respuestas individuales están cifradas, se mantienen fuera del Coach de IA y de las exportaciones, y nunca se comparten.", + "attributionLabel": "Fuente", + "choosePrompt": "Elige una revisión", + "start": "Empezar", + "back": "Atrás", + "stem": "Durante las últimas 2 semanas, ¿con qué frecuencia te han molestado los siguientes problemas?", + "submit": "Ver mi resultado", + "submitting": "Guardando…", + "saved": "Guardado", + "error": "Algo salió mal. Inténtalo de nuevo.", + "considerProfessional": "Tu puntuación está en un rango en el que hablar con un profesional de salud puede ayudar. Es una sugerencia amable, no un diagnóstico.", + "instrument": { + "phq9": "PHQ-9 — ánimo", + "gad7": "GAD-7 — preocupación" + }, + "instrumentSub": { + "phq9": "Cribado de depresión, 9 preguntas", + "gad7": "Cribado de ansiedad, 7 preguntas" + }, + "options": { + "0": "Nunca", + "1": "Varios días", + "2": "Más de la mitad de los días", + "3": "Casi todos los días" + }, + "functionalTitle": "Si marcaste algún problema, ¿cuánta dificultad te han causado para trabajar, ocuparte de la casa o relacionarte con otras personas?", + "functional": { + "0": "Ninguna dificultad", + "1": "Algo de dificultad", + "2": "Mucha dificultad", + "3": "Dificultad extrema" + }, + "result": { + "title": "Tu resultado", + "totalLabel": "Puntuación total", + "bandLabel": "Rango para este cribado", + "takeAnother": "Hacer otra revisión" + }, + "band": { + "PHQ9": { + "minimal": "Ninguna / mínima", + "mild": "Leve", + "moderate": "Moderada", + "modSevere": "Moderadamente grave", + "severe": "Grave" + }, + "GAD7": { + "minimal": "Ninguna / mínima", + "mild": "Leve", + "moderate": "Moderada", + "severe": "Grave" + } + }, + "history": { + "title": "Tu historial", + "empty": "Aún no hay revisiones. Tu tendencia aparecerá aquí cuando completes una." + }, + "crisis": { + "title": "No tienes que afrontar esto a solas", + "intro": "Indicaste pensamientos de estar mejor muerto o de hacerte daño. Hay apoyo disponible ahora mismo, y pedir ayuda es una señal de fortaleza.", + "ifDanger": "Si estás en peligro inmediato, llama ahora al {emergency}.", + "resourcesTitle": "Personas que pueden ayudar" + }, + "items": { + "phq9": { + "1": "Little interest or pleasure in doing things", + "2": "Feeling down, depressed, or hopeless", + "3": "Trouble falling or staying asleep, or sleeping too much", + "4": "Feeling tired or having little energy", + "5": "Poor appetite or overeating", + "6": "Feeling bad about yourself — or that you are a failure or have let yourself or your family down", + "7": "Trouble concentrating on things, such as reading the newspaper or watching television", + "8": "Moving or speaking so slowly that other people could have noticed. Or the opposite — being so fidgety or restless that you have been moving around a lot more than usual", + "9": "Thoughts that you would be better off dead, or of hurting yourself in some way" + }, + "gad7": { + "1": "Feeling nervous, anxious, or on edge", + "2": "Not being able to stop or control worrying", + "3": "Worrying too much about different things", + "4": "Trouble relaxing", + "5": "Being so restless that it is hard to sit still", + "6": "Becoming easily annoyed or irritable", + "7": "Feeling afraid as if something awful might happen" + } + }, + "crisisResource": { + "telefonSeelsorge": { + "name": "TelefonSeelsorge (24/7, free, confidential)" + }, + "nummerGegenKummer": { + "name": "Nummer gegen Kummer (for young people)" + }, + "krisenchat": { + "name": "krisenchat (24/7 chat, young people)" + }, + "findahelpline": { + "name": "Find a helpline (worldwide directory)" + }, + "euEmotionalSupport": { + "name": "Emotional-support helpline" + }, + "lifeline988": { + "name": "988 Suicide & Crisis Lifeline" + }, + "crisisTextLine": { + "name": "Crisis Text Line" + } + } } } diff --git a/messages/fr.json b/messages/fr.json index 5b953e533..9d590edec 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -575,7 +575,13 @@ "bulkDeleteSuccess": "{count} mesures supprimées", "bulkDeleteError": "Erreur lors de la suppression des mesures sélectionnées", "bulkDeleteConfirmTitle": "Supprimer {count} mesures ?", - "bulkDeleteConfirmBody": "Les {count} mesures sélectionnées seront définitivement supprimées." + "bulkDeleteConfirmBody": "Les {count} mesures sélectionnées seront définitivement supprimées.", + "typePhq9Score": "Score PHQ-9", + "typeGad7Score": "Score GAD-7", + "typeGripStrength": "Force de préhension", + "typePainNrs": "Douleur (0–10)", + "typeWaistCircumference": "Tour de taille", + "typeWaistToHeight": "Rapport taille-taille" }, "mood": { "title": "Humeur", @@ -7090,8 +7096,16 @@ "hemoglobin": "La protéine des globules rouges qui transporte l'oxygène.", "hematocrit": "La fraction du volume sanguin constituée de globules rouges.", "wbc": "Nombre total de globules blancs, la défense circulante du système immunitaire.", - "platelets": "Fragments cellulaires qui s'agrègent pour former des caillots sanguins." - } + "platelets": "Fragments cellulaires qui s'agrègent pour former des caillots sanguins.", + "apob": "Compte directement les particules athérogènes ; souvent un meilleur marqueur de risque que le LDL-C en cas de discordance.", + "lp-a": "Facteur de risque en grande partie héréditaire et permanent — mesuré une seule fois en général. À exprimer en unités molaires (nmol/L).", + "fasting-insulin": "Se combine à la glycémie à jeun pour estimer la résistance à l'insuline (HOMA-IR).", + "omega-3-index": "Part d'EPA+DHA dans les globules rouges ; 8–12 % est la plage cardioprotectrice habituellement citée." + }, + "apob": "Apolipoprotéine B (ApoB)", + "lp-a": "Lipoprotéine(a)", + "fasting-insulin": "Insuline à jeun", + "omega-3-index": "Indice oméga-3" }, "noteLoadFailed": "La note n’a pas pu être chargée ; elle restera inchangée à l’enregistrement.", "showingLatestOf": "Affichage des {shown} derniers résultats sur {total}", @@ -7426,5 +7440,118 @@ "reload": "Recharger", "dismiss": "Ignorer" } + }, + "mentalHealth": { + "pageTitle": "Point sur le bien-être mental", + "pageDescription": "Deux auto-évaluations validées et facultatives — PHQ-9 (humeur) et GAD-7 (inquiétude) — en complément de ton suivi de l'humeur. Ce qui compte, c'est la tendance dans le temps, pas un chiffre isolé.", + "disclaimer": "Il s'agit d'une auto-évaluation de dépistage, pas d'un diagnostic. Un score élevé ou bas ne signifie pas que tu as ou n'as pas un trouble. Seul un professionnel de santé qualifié peut poser un diagnostic. En cas de doute, parles-en à ton médecin.", + "optInNote": "Facultatif et privé : tes réponses individuelles sont chiffrées, tenues à l'écart du Coach IA et des exports, et ne sont jamais partagées.", + "attributionLabel": "Source", + "choosePrompt": "Choisis un bilan", + "start": "Commencer", + "back": "Retour", + "stem": "Au cours des 2 dernières semaines, à quelle fréquence as-tu été gêné(e) par les problèmes suivants ?", + "submit": "Voir mon résultat", + "submitting": "Enregistrement…", + "saved": "Enregistré", + "error": "Une erreur est survenue. Réessaie.", + "considerProfessional": "Ton score se situe dans une plage où parler à un professionnel de santé peut aider. C'est une suggestion bienveillante, pas un diagnostic.", + "instrument": { + "phq9": "PHQ-9 — humeur", + "gad7": "GAD-7 — inquiétude" + }, + "instrumentSub": { + "phq9": "Dépistage de la dépression, 9 questions", + "gad7": "Dépistage de l'anxiété, 7 questions" + }, + "options": { + "0": "Jamais", + "1": "Plusieurs jours", + "2": "Plus de la moitié des jours", + "3": "Presque tous les jours" + }, + "functionalTitle": "Si tu as coché des problèmes, à quel point t'ont-ils gêné(e) dans ton travail, à la maison ou dans tes relations avec les autres ?", + "functional": { + "0": "Pas du tout gênant", + "1": "Un peu gênant", + "2": "Très gênant", + "3": "Extrêmement gênant" + }, + "result": { + "title": "Ton résultat", + "totalLabel": "Score total", + "bandLabel": "Plage pour ce dépistage", + "takeAnother": "Faire un autre bilan" + }, + "band": { + "PHQ9": { + "minimal": "Aucune / minimale", + "mild": "Légère", + "moderate": "Modérée", + "modSevere": "Modérément sévère", + "severe": "Sévère" + }, + "GAD7": { + "minimal": "Aucune / minimale", + "mild": "Légère", + "moderate": "Modérée", + "severe": "Sévère" + } + }, + "history": { + "title": "Ton historique", + "empty": "Aucun bilan pour le moment. Ta tendance apparaîtra ici dès que tu en auras terminé un." + }, + "crisis": { + "title": "Tu n'as pas à affronter cela seul(e)", + "intro": "Tu as indiqué des pensées qu'il vaudrait mieux être mort(e) ou de te faire du mal. De l'aide est disponible dès maintenant, et demander de l'aide est un signe de force.", + "ifDanger": "Si tu es en danger immédiat, appelle le {emergency} maintenant.", + "resourcesTitle": "Des personnes qui peuvent aider" + }, + "items": { + "phq9": { + "1": "Little interest or pleasure in doing things", + "2": "Feeling down, depressed, or hopeless", + "3": "Trouble falling or staying asleep, or sleeping too much", + "4": "Feeling tired or having little energy", + "5": "Poor appetite or overeating", + "6": "Feeling bad about yourself — or that you are a failure or have let yourself or your family down", + "7": "Trouble concentrating on things, such as reading the newspaper or watching television", + "8": "Moving or speaking so slowly that other people could have noticed. Or the opposite — being so fidgety or restless that you have been moving around a lot more than usual", + "9": "Thoughts that you would be better off dead, or of hurting yourself in some way" + }, + "gad7": { + "1": "Feeling nervous, anxious, or on edge", + "2": "Not being able to stop or control worrying", + "3": "Worrying too much about different things", + "4": "Trouble relaxing", + "5": "Being so restless that it is hard to sit still", + "6": "Becoming easily annoyed or irritable", + "7": "Feeling afraid as if something awful might happen" + } + }, + "crisisResource": { + "telefonSeelsorge": { + "name": "TelefonSeelsorge (24/7, free, confidential)" + }, + "nummerGegenKummer": { + "name": "Nummer gegen Kummer (for young people)" + }, + "krisenchat": { + "name": "krisenchat (24/7 chat, young people)" + }, + "findahelpline": { + "name": "Find a helpline (worldwide directory)" + }, + "euEmotionalSupport": { + "name": "Emotional-support helpline" + }, + "lifeline988": { + "name": "988 Suicide & Crisis Lifeline" + }, + "crisisTextLine": { + "name": "Crisis Text Line" + } + } } } diff --git a/messages/it.json b/messages/it.json index 1c446bda3..2f3d30a4f 100644 --- a/messages/it.json +++ b/messages/it.json @@ -575,7 +575,13 @@ "bulkDeleteSuccess": "{count} misurazioni eliminate", "bulkDeleteError": "Errore durante l'eliminazione delle misurazioni selezionate", "bulkDeleteConfirmTitle": "Eliminare {count} misurazioni?", - "bulkDeleteConfirmBody": "Le {count} misurazioni selezionate verranno eliminate definitivamente." + "bulkDeleteConfirmBody": "Le {count} misurazioni selezionate verranno eliminate definitivamente.", + "typePhq9Score": "Punteggio PHQ-9", + "typeGad7Score": "Punteggio GAD-7", + "typeGripStrength": "Forza di presa", + "typePainNrs": "Dolore (0–10)", + "typeWaistCircumference": "Circonferenza vita", + "typeWaistToHeight": "Rapporto vita-altezza" }, "mood": { "title": "Umore", @@ -7090,8 +7096,16 @@ "hemoglobin": "La proteina dei globuli rossi che trasporta l'ossigeno.", "hematocrit": "La frazione del volume sanguigno costituita da globuli rossi.", "wbc": "Conteggio totale dei globuli bianchi, la difesa circolante del sistema immunitario.", - "platelets": "Frammenti cellulari che si aggregano per formare coaguli di sangue." - } + "platelets": "Frammenti cellulari che si aggregano per formare coaguli di sangue.", + "apob": "Conta direttamente le particelle aterogene; spesso un marcatore di rischio migliore del LDL-C quando i due discordano.", + "lp-a": "Fattore di rischio in gran parte ereditario e permanente — di solito misurato una volta. Riportare in unità molari (nmol/L).", + "fasting-insulin": "Si abbina alla glicemia a digiuno per stimare la resistenza insulinica (HOMA-IR).", + "omega-3-index": "Quota di EPA+DHA nei globuli rossi; 8–12% è l'intervallo cardioprotettivo comunemente citato." + }, + "apob": "Apolipoproteina B (ApoB)", + "lp-a": "Lipoproteina(a)", + "fasting-insulin": "Insulina a digiuno", + "omega-3-index": "Indice omega-3" }, "noteLoadFailed": "Impossibile caricare la nota; resterà invariata al salvataggio.", "showingLatestOf": "Visualizzazione degli ultimi {shown} risultati su {total}", @@ -7426,5 +7440,118 @@ "reload": "Ricarica", "dismiss": "Ignora" } + }, + "mentalHealth": { + "pageTitle": "Check-in sul benessere mentale", + "pageDescription": "Due autovalutazioni validate e facoltative — PHQ-9 (umore) e GAD-7 (preoccupazione) — accanto al tuo diario dell'umore. Ciò che conta è l'andamento nel tempo, non un singolo numero.", + "disclaimer": "Questa è un'autovalutazione di screening, non una diagnosi. Un punteggio alto o basso non significa che tu abbia o non abbia un disturbo. Solo un professionista sanitario qualificato può fare una diagnosi. In caso di dubbi, parlane con il tuo medico.", + "optInNote": "Facoltativo e privato: le tue risposte individuali sono cifrate, tenute fuori dal Coach IA e dalle esportazioni, e non vengono mai condivise.", + "attributionLabel": "Fonte", + "choosePrompt": "Scegli un check-in", + "start": "Inizia", + "back": "Indietro", + "stem": "Nelle ultime 2 settimane, con quale frequenza ti hanno disturbato i seguenti problemi?", + "submit": "Vedi il mio risultato", + "submitting": "Salvataggio…", + "saved": "Salvato", + "error": "Qualcosa è andato storto. Riprova.", + "considerProfessional": "Il tuo punteggio è in un intervallo in cui parlare con un professionista sanitario può aiutare. È un suggerimento gentile, non una diagnosi.", + "instrument": { + "phq9": "PHQ-9 — umore", + "gad7": "GAD-7 — preoccupazione" + }, + "instrumentSub": { + "phq9": "Screening della depressione, 9 domande", + "gad7": "Screening dell'ansia, 7 domande" + }, + "options": { + "0": "Per niente", + "1": "Alcuni giorni", + "2": "Più della metà dei giorni", + "3": "Quasi ogni giorno" + }, + "functionalTitle": "Se hai indicato dei problemi, quanto ti hanno reso difficile lavorare, occuparti della casa o andare d'accordo con gli altri?", + "functional": { + "0": "Per niente difficile", + "1": "Un po' difficile", + "2": "Molto difficile", + "3": "Estremamente difficile" + }, + "result": { + "title": "Il tuo risultato", + "totalLabel": "Punteggio totale", + "bandLabel": "Intervallo per questo screening", + "takeAnother": "Fai un altro check-in" + }, + "band": { + "PHQ9": { + "minimal": "Nessuna / minima", + "mild": "Lieve", + "moderate": "Moderata", + "modSevere": "Moderatamente grave", + "severe": "Grave" + }, + "GAD7": { + "minimal": "Nessuna / minima", + "mild": "Lieve", + "moderate": "Moderata", + "severe": "Grave" + } + }, + "history": { + "title": "La tua cronologia", + "empty": "Ancora nessun check-in. Il tuo andamento apparirà qui dopo che ne avrai completato uno." + }, + "crisis": { + "title": "Non devi affrontare tutto questo da solo/a", + "intro": "Hai indicato pensieri che sarebbe meglio essere morto/a o di farti del male. Un supporto è disponibile proprio ora, e chiedere aiuto è un segno di forza.", + "ifDanger": "Se sei in pericolo immediato, chiama subito il {emergency}.", + "resourcesTitle": "Persone che possono aiutare" + }, + "items": { + "phq9": { + "1": "Little interest or pleasure in doing things", + "2": "Feeling down, depressed, or hopeless", + "3": "Trouble falling or staying asleep, or sleeping too much", + "4": "Feeling tired or having little energy", + "5": "Poor appetite or overeating", + "6": "Feeling bad about yourself — or that you are a failure or have let yourself or your family down", + "7": "Trouble concentrating on things, such as reading the newspaper or watching television", + "8": "Moving or speaking so slowly that other people could have noticed. Or the opposite — being so fidgety or restless that you have been moving around a lot more than usual", + "9": "Thoughts that you would be better off dead, or of hurting yourself in some way" + }, + "gad7": { + "1": "Feeling nervous, anxious, or on edge", + "2": "Not being able to stop or control worrying", + "3": "Worrying too much about different things", + "4": "Trouble relaxing", + "5": "Being so restless that it is hard to sit still", + "6": "Becoming easily annoyed or irritable", + "7": "Feeling afraid as if something awful might happen" + } + }, + "crisisResource": { + "telefonSeelsorge": { + "name": "TelefonSeelsorge (24/7, free, confidential)" + }, + "nummerGegenKummer": { + "name": "Nummer gegen Kummer (for young people)" + }, + "krisenchat": { + "name": "krisenchat (24/7 chat, young people)" + }, + "findahelpline": { + "name": "Find a helpline (worldwide directory)" + }, + "euEmotionalSupport": { + "name": "Emotional-support helpline" + }, + "lifeline988": { + "name": "988 Suicide & Crisis Lifeline" + }, + "crisisTextLine": { + "name": "Crisis Text Line" + } + } } } diff --git a/messages/pl.json b/messages/pl.json index cb6fd0d43..865758a4d 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -575,7 +575,13 @@ "bulkDeleteSuccess": "Usunięto pomiary: {count}", "bulkDeleteError": "Błąd podczas usuwania wybranych pomiarów", "bulkDeleteConfirmTitle": "Usunąć pomiary ({count})?", - "bulkDeleteConfirmBody": "Wybrane pomiary ({count}) zostaną trwale usunięte." + "bulkDeleteConfirmBody": "Wybrane pomiary ({count}) zostaną trwale usunięte.", + "typePhq9Score": "Wynik PHQ-9", + "typeGad7Score": "Wynik GAD-7", + "typeGripStrength": "Siła chwytu", + "typePainNrs": "Ból (0–10)", + "typeWaistCircumference": "Obwód talii", + "typeWaistToHeight": "Stosunek talii do wzrostu" }, "mood": { "title": "Nastrój", @@ -7090,8 +7096,16 @@ "hemoglobin": "Białko czerwonych krwinek transportujące tlen.", "hematocrit": "Udział czerwonych krwinek w objętości krwi.", "wbc": "Całkowita liczba białych krwinek, krążącej obrony układu odpornościowego.", - "platelets": "Fragmenty komórkowe, które zlepiają się, tworząc skrzepy krwi." - } + "platelets": "Fragmenty komórkowe, które zlepiają się, tworząc skrzepy krwi.", + "apob": "Bezpośrednio liczy cząstki aterogenne; często lepszy marker ryzyka niż LDL-C, gdy się różnią.", + "lp-a": "W dużej mierze dziedziczny, dożywotni czynnik ryzyka — zwykle mierzony raz. Podawaj w jednostkach molowych (nmol/L).", + "fasting-insulin": "Łączy się z glukozą na czczo do oszacowania insulinooporności (HOMA-IR).", + "omega-3-index": "Udział EPA+DHA w czerwonych krwinkach; 8–12% to powszechnie podawany zakres kardioprotekcyjny." + }, + "apob": "Apolipoproteina B (ApoB)", + "lp-a": "Lipoproteina(a)", + "fasting-insulin": "Insulina na czczo", + "omega-3-index": "Indeks omega-3" }, "noteLoadFailed": "Nie udało się wczytać notatki; pozostanie niezmieniona po zapisaniu.", "showingLatestOf": "Wyświetlanie najnowszych {shown} z {total} wyników", @@ -7426,5 +7440,118 @@ "reload": "Załaduj ponownie", "dismiss": "Odrzuć" } + }, + "mentalHealth": { + "pageTitle": "Sprawdzenie dobrostanu psychicznego", + "pageDescription": "Dwa zwalidowane, dobrowolne testy — PHQ-9 (nastrój) i GAD-7 (lęk) — obok Twojego dziennika nastroju. Liczy się trend w czasie, a nie pojedynczy wynik.", + "disclaimer": "To przesiewowa samoocena, a nie diagnoza. Wysoki lub niski wynik nie oznacza, że masz lub nie masz danego schorzenia. Tylko wykwalifikowany pracownik ochrony zdrowia może postawić diagnozę. W razie wątpliwości porozmawiaj z lekarzem.", + "optInNote": "Dobrowolne i prywatne: Twoje poszczególne odpowiedzi są szyfrowane, pozostają poza Coachem AI i eksportami i nigdy nie są udostępniane.", + "attributionLabel": "Źródło", + "choosePrompt": "Wybierz sprawdzenie", + "start": "Rozpocznij", + "back": "Wstecz", + "stem": "Jak często w ciągu ostatnich 2 tygodni dokuczały Ci następujące problemy?", + "submit": "Zobacz mój wynik", + "submitting": "Zapisywanie…", + "saved": "Zapisano", + "error": "Coś poszło nie tak. Spróbuj ponownie.", + "considerProfessional": "Twój wynik mieści się w zakresie, w którym rozmowa ze specjalistą może pomóc. To życzliwa sugestia, a nie diagnoza.", + "instrument": { + "phq9": "PHQ-9 — nastrój", + "gad7": "GAD-7 — lęk" + }, + "instrumentSub": { + "phq9": "Przesiew depresji, 9 pytań", + "gad7": "Przesiew lęku, 7 pytań" + }, + "options": { + "0": "Wcale", + "1": "Kilka dni", + "2": "Więcej niż połowę dni", + "3": "Niemal codziennie" + }, + "functionalTitle": "Jeśli zaznaczyłeś(-aś) jakiekolwiek problemy, na ile utrudniły Ci one pracę, prowadzenie domu lub kontakty z innymi ludźmi?", + "functional": { + "0": "Wcale nie utrudniły", + "1": "Trochę utrudniły", + "2": "Bardzo utrudniły", + "3": "Skrajnie utrudniły" + }, + "result": { + "title": "Twój wynik", + "totalLabel": "Wynik łączny", + "bandLabel": "Zakres dla tego przesiewu", + "takeAnother": "Wykonaj kolejne sprawdzenie" + }, + "band": { + "PHQ9": { + "minimal": "Brak / minimalny", + "mild": "Łagodny", + "moderate": "Umiarkowany", + "modSevere": "Umiarkowanie ciężki", + "severe": "Ciężki" + }, + "GAD7": { + "minimal": "Brak / minimalny", + "mild": "Łagodny", + "moderate": "Umiarkowany", + "severe": "Ciężki" + } + }, + "history": { + "title": "Twoja historia", + "empty": "Brak sprawdzeń. Twój trend pojawi się tutaj po ukończeniu pierwszego." + }, + "crisis": { + "title": "Nie musisz mierzyć się z tym sam(a)", + "intro": "Wskazałeś(-aś) myśli, że lepiej byłoby nie żyć lub zrobić sobie krzywdę. Wsparcie jest dostępne już teraz, a sięgnięcie po pomoc to oznaka siły.", + "ifDanger": "Jeśli grozi Ci bezpośrednie niebezpieczeństwo, zadzwoń teraz pod {emergency}.", + "resourcesTitle": "Osoby, które mogą pomóc" + }, + "items": { + "phq9": { + "1": "Little interest or pleasure in doing things", + "2": "Feeling down, depressed, or hopeless", + "3": "Trouble falling or staying asleep, or sleeping too much", + "4": "Feeling tired or having little energy", + "5": "Poor appetite or overeating", + "6": "Feeling bad about yourself — or that you are a failure or have let yourself or your family down", + "7": "Trouble concentrating on things, such as reading the newspaper or watching television", + "8": "Moving or speaking so slowly that other people could have noticed. Or the opposite — being so fidgety or restless that you have been moving around a lot more than usual", + "9": "Thoughts that you would be better off dead, or of hurting yourself in some way" + }, + "gad7": { + "1": "Feeling nervous, anxious, or on edge", + "2": "Not being able to stop or control worrying", + "3": "Worrying too much about different things", + "4": "Trouble relaxing", + "5": "Being so restless that it is hard to sit still", + "6": "Becoming easily annoyed or irritable", + "7": "Feeling afraid as if something awful might happen" + } + }, + "crisisResource": { + "telefonSeelsorge": { + "name": "TelefonSeelsorge (24/7, free, confidential)" + }, + "nummerGegenKummer": { + "name": "Nummer gegen Kummer (for young people)" + }, + "krisenchat": { + "name": "krisenchat (24/7 chat, young people)" + }, + "findahelpline": { + "name": "Find a helpline (worldwide directory)" + }, + "euEmotionalSupport": { + "name": "Emotional-support helpline" + }, + "lifeline988": { + "name": "988 Suicide & Crisis Lifeline" + }, + "crisisTextLine": { + "name": "Crisis Text Line" + } + } } } diff --git a/src/app/api/mental-health/assessments/route.ts b/src/app/api/mental-health/assessments/route.ts index aa3cc644e..ea78e2f08 100644 --- a/src/app/api/mental-health/assessments/route.ts +++ b/src/app/api/mental-health/assessments/route.ts @@ -40,7 +40,10 @@ import { type InstrumentId, } from "@/lib/mental-health/instruments"; import { crisisResourcesForLocale } from "@/lib/mental-health/crisis-resources"; -import type { MeasurementType, MeasurementSource } from "@/generated/prisma/client"; +import type { + MeasurementType, + MeasurementSource, +} from "@/generated/prisma/client"; function shapeRow(row: { id: string; @@ -81,7 +84,11 @@ export const GET = apiHandler(async (request: NextRequest) => { } const { instrument, limit, offset } = parsed.data; const rows = await prisma.mentalHealthAssessment.findMany({ - where: { userId: user.id, deletedAt: null, ...(instrument ? { instrument } : {}) }, + where: { + userId: user.id, + deletedAt: null, + ...(instrument ? { instrument } : {}), + }, orderBy: { takenAt: "desc" }, take: limit ?? 100, skip: offset ?? 0, diff --git a/src/app/insights/mental-wellbeing/page.tsx b/src/app/insights/mental-wellbeing/page.tsx new file mode 100644 index 000000000..fb6f1039d --- /dev/null +++ b/src/app/insights/mental-wellbeing/page.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { MentalWellbeing } from "@/components/mental-health/mental-wellbeing"; + +/** + * v1.25 — `/insights/mental-wellbeing`. + * + * Opt-in PHQ-9 / GAD-7 screener surface, beside mood tracking. The screen owns + * its own questionnaire + result + crisis-resource flow; it deliberately does + * NOT mount the shared Coach-launch assessment card (mental-health item content + * is kept out of the AI Coach by construction). + */ +export default function MentalWellbeingPage() { + return ( +
+ +
+ ); +} diff --git a/src/components/mental-health/mental-wellbeing.tsx b/src/components/mental-health/mental-wellbeing.tsx new file mode 100644 index 000000000..21974aac3 --- /dev/null +++ b/src/components/mental-health/mental-wellbeing.tsx @@ -0,0 +1,360 @@ +"use client"; + +/** + * v1.25 — opt-in mental-health screener surface (PHQ-9 / GAD-7). + * + * Beside mood tracking, never replacing it; framed as a screening self-check, + * not a diagnosis. The per-item answers stay on the device until submit, the + * server encrypts them, and only the total trend is persisted as a measurement. + * + * SAFETY: this surface is deliberately kept OFF the AI Coach — the shared + * Coach-launch assessment card is intentionally NOT used here, so a depression / + * anxiety screen never invites an AI conversation about item content. On a + * positive PHQ-9 item-9 the server returns a calm, locale-aware crisis-resource + * set which is rendered immediately below the result. + */ +import { useMemo, useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + +import { useTranslations, useFormatters } from "@/lib/i18n/context"; +import { apiGet, apiPost } from "@/lib/api/api-fetch"; +import { queryKeys } from "@/lib/query-keys"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + INSTRUMENTS, + PHQ_GAD_ATTRIBUTION, + type InstrumentId, +} from "@/lib/mental-health/instruments"; + +type Phase = "choose" | "form" | "result"; + +interface AssessmentRow { + id: string; + instrument: InstrumentId; + totalScore: number; + severityBand: string; + item9Flagged: boolean; + takenAt: string; +} + +interface CrisisSet { + emergencyNumber: string; + resources: { id: string; contacts: string[] }[]; +} + +interface CreateResponse { + assessment: AssessmentRow; + actionThreshold: number; + crisis: CrisisSet | null; +} + +const SCALE = [0, 1, 2, 3] as const; +const FUNCTIONAL = [0, 1, 2, 3] as const; + +export function MentalWellbeing() { + const { t, locale } = useTranslations(); + const { date: formatDate } = useFormatters(); + const queryClient = useQueryClient(); + + const [phase, setPhase] = useState("choose"); + const [instrument, setInstrument] = useState("PHQ9"); + const [items, setItems] = useState([]); + const [functional, setFunctional] = useState(null); + const [result, setResult] = useState(null); + + const lower = (id: InstrumentId) => (id === "PHQ9" ? "phq9" : "gad7"); + + const { data: history } = useQuery({ + queryKey: queryKeys.mentalHealthAssessments(), + queryFn: () => + apiGet<{ assessments: AssessmentRow[] }>( + "/api/mental-health/assessments", + ), + }); + + const mutation = useMutation({ + mutationFn: (body: unknown) => + apiPost("/api/mental-health/assessments", body), + onSuccess: (data) => { + setResult(data); + setPhase("result"); + void queryClient.invalidateQueries({ + queryKey: queryKeys.mentalHealthAssessments(), + }); + }, + }); + + function begin(id: InstrumentId) { + setInstrument(id); + setItems(Array(INSTRUMENTS[id].itemCount).fill(-1)); + setFunctional(null); + setResult(null); + setPhase("form"); + } + + const complete = items.length > 0 && items.every((v) => v >= 0); + + function submit() { + if (!complete) return; + mutation.mutate({ + instrument, + items, + ...(functional !== null ? { functionalDifficulty: functional } : {}), + locale, + }); + } + + const itemCount = INSTRUMENTS[instrument].itemCount; + const itemIndexes = useMemo( + () => Array.from({ length: itemCount }, (_, i) => i), + [itemCount], + ); + + return ( +
+
+

+ {t("mentalHealth.pageTitle")} +

+

+ {t("mentalHealth.pageDescription")} +

+

+ {t("mentalHealth.disclaimer")} +

+

+ {t("mentalHealth.optInNote")} +

+
+ + {phase === "choose" && ( +
+

+ {t("mentalHealth.choosePrompt")} +

+ {(["PHQ9", "GAD7"] as InstrumentId[]).map((id) => ( + + +
+ + {t(`mentalHealth.instrument.${lower(id)}`)} + + + {t(`mentalHealth.instrumentSub.${lower(id)}`)} + +
+ +
+
+ ))} +
+ )} + + {phase === "form" && ( +
+
+ + + {t(`mentalHealth.instrument.${lower(instrument)}`)} + +
+

{t("mentalHealth.stem")}

+ +
    + {itemIndexes.map((i) => ( +
  1. + + {i + 1}.{" "} + {t(`mentalHealth.items.${lower(instrument)}.${i + 1}`)} + +
    + {SCALE.map((v) => ( + + ))} +
    +
  2. + ))} +
+ +
+ {t("mentalHealth.functionalTitle")} +
+ {FUNCTIONAL.map((v) => ( + + ))} +
+
+ + {mutation.isError && ( +

+ {t("mentalHealth.error")} +

+ )} + +
+ )} + + {phase === "result" && result && ( +
+

+ {t("mentalHealth.saved")} +

+ + + {t("mentalHealth.result.title")} + + +
+ + {t("mentalHealth.result.totalLabel")} + + + {result.assessment.totalScore} + +
+
+ + {t("mentalHealth.result.bandLabel")} + + + {t( + `mentalHealth.band.${result.assessment.instrument}.${result.assessment.severityBand}`, + )} + +
+ {result.assessment.totalScore >= result.actionThreshold && ( +

+ {t("mentalHealth.considerProfessional")} +

+ )} +
+
+ + {result.crisis && ( + + + + {t("mentalHealth.crisis.title")} + + + +

{t("mentalHealth.crisis.intro")}

+

+ {t("mentalHealth.crisis.ifDanger", { + emergency: result.crisis.emergencyNumber, + })} +

+
+ + {t("mentalHealth.crisis.resourcesTitle")} + +
    + {result.crisis.resources.map((r) => ( +
  • + + {t(`mentalHealth.crisisResource.${r.id}.name`)} + + + {r.contacts.join(" · ")} + +
  • + ))} +
+
+
+
+ )} + +

+ + {t("mentalHealth.attributionLabel")}: + {" "} + {PHQ_GAD_ATTRIBUTION} +

+ + +
+ )} + +
+

+ {t("mentalHealth.history.title")} +

+ {!history || history.assessments.length === 0 ? ( +

+ {t("mentalHealth.history.empty")} +

+ ) : ( +
    + {history.assessments.map((row) => ( +
  • + + {t(`mentalHealth.instrument.${lower(row.instrument)}`)} + + + {t(`mentalHealth.band.${row.instrument}.${row.severityBand}`)}{" "} + · {formatDate(row.takenAt)} + +
  • + ))} +
+ )} +
+
+ ); +} diff --git a/src/lib/measurements/__tests__/apple-health-mapping.test.ts b/src/lib/measurements/__tests__/apple-health-mapping.test.ts index 0c0a7ef6b..66544ea21 100644 --- a/src/lib/measurements/__tests__/apple-health-mapping.test.ts +++ b/src/lib/measurements/__tests__/apple-health-mapping.test.ts @@ -77,6 +77,17 @@ const MEASUREMENT_TYPES_WITHOUT_HK_COUNTERPART = new Set([ // the Oura API (source = OURA), never from HealthKit: Apple ships no // resilience metric. No HK mapping by design. "RESILIENCE", + // v1.25 — clinical-signals wave. The mental-health screener totals are + // derived from an in-app questionnaire, and grip strength / pain NRS / waist + // circumference + waist-to-height are manual / instrument readings — none + // ships a first-class HealthKit quantity we ingest, so they have no HK + // mapping by design. + "PHQ9_SCORE", + "GAD7_SCORE", + "GRIP_STRENGTH", + "PAIN_NRS", + "WAIST_CIRCUMFERENCE", + "WAIST_TO_HEIGHT", ]); describe("APPLE_HEALTH_TYPE_MAP", () => { diff --git a/src/lib/signals/registry.ts b/src/lib/signals/registry.ts index cbb4f5948..70d472f4f 100644 --- a/src/lib/signals/registry.ts +++ b/src/lib/signals/registry.ts @@ -1222,7 +1222,8 @@ export const SIGNALS: Record = { }, fhir: { loinc: "44261-6", - display: "Patient Health Questionnaire 9 item (PHQ-9) total score [Reported]", + display: + "Patient Health Questionnaire 9 item (PHQ-9) total score [Reported]", unit: "{score}", category: "survey", }, diff --git a/src/lib/validations/mental-health.ts b/src/lib/validations/mental-health.ts index 73f24442a..3cd8bfefc 100644 --- a/src/lib/validations/mental-health.ts +++ b/src/lib/validations/mental-health.ts @@ -5,7 +5,10 @@ * it is narrowed from the session in the handler (CLAUDE.md convention). */ import { z } from "zod/v4"; -import { INSTRUMENTS, type InstrumentId } from "@/lib/mental-health/instruments"; +import { + INSTRUMENTS, + type InstrumentId, +} from "@/lib/mental-health/instruments"; export const assessmentInstrumentEnum = z.enum(["PHQ9", "GAD7"]);