diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml
index ab95da7d7..3ae3bbb86 100644
--- a/docs/api/openapi.yaml
+++ b/docs/api/openapi.yaml
@@ -2103,6 +2103,8 @@ paths:
- PAIN_NRS
- WAIST_CIRCUMFERENCE
- WAIST_TO_HEIGHT
+ - WHO5_SCORE
+ - SCI_SCORE
- in: query
name: from
schema:
@@ -2299,6 +2301,8 @@ paths:
- PAIN_NRS
- WAIST_CIRCUMFERENCE
- WAIST_TO_HEIGHT
+ - WHO5_SCORE
+ - SCI_SCORE
value:
type: number
measuredAt:
@@ -5878,6 +5882,8 @@ paths:
- PAIN_NRS
- WAIST_CIRCUMFERENCE
- WAIST_TO_HEIGHT
+ - WHO5_SCORE
+ - SCI_SCORE
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."
@@ -8116,8 +8122,8 @@ paths:
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.
+ description: Returns PHQ-9 / GAD-7 / WHO-5 / SCI administrations (newest first). Totals + bands + flags only; raw item
+ answers are never returned.
parameters:
- in: query
name: instrument
@@ -8126,6 +8132,8 @@ paths:
enum:
- PHQ9
- GAD7
+ - WHO5
+ - SCI
- in: query
name: limit
schema:
@@ -8151,10 +8159,11 @@ paths:
post:
tags:
- Mental health
- summary: Record a completed PHQ-9 / GAD-7 screener
+ summary: Record a completed screener
description: Stores one administration (item answers encrypted) and writes the derived total as a server-owned
- PHQ9_SCORE / GAD7_SCORE measurement (source COMPUTED — never client-writable). On a positive PHQ-9 item-9 the
- response carries the locale-aware crisis-resource set.
+ PHQ9_SCORE / GAD7_SCORE / WHO5_SCORE / SCI_SCORE measurement (source COMPUTED — never client-writable). WHO-5
+ and SCI totals run HIGHER = better; their follow-up thresholds point downwards (≤ 50 / ≤ 16). On a positive
+ PHQ-9 item-9 the response carries the locale-aware crisis-resource set.
requestBody:
content:
application/json:
@@ -8166,6 +8175,8 @@ paths:
enum:
- PHQ9
- GAD7
+ - WHO5
+ - SCI
items:
minItems: 1
maxItems: 12
@@ -8173,7 +8184,7 @@ paths:
items:
type: integer
minimum: 0
- maximum: 3
+ maximum: 5
functionalDifficulty:
type: integer
minimum: 0
@@ -11158,9 +11169,11 @@ components:
- BODY_MASS_INDEX
- PHQ9_SCORE
- GAD7_SCORE
- description: Auto-resolve target metric (vitals + body-composition family + the PHQ-9 / GAD-7 screenings). BP resolves
- on BLOOD_PRESSURE_SYS (the SYS row is the 'a BP was measured' sentinel); a screening reminder resolves on the
- server-written PHQ9_SCORE / GAD7_SCORE row when a check-in completes. Omit for a free-text Vorsorge that
+ - WHO5_SCORE
+ - SCI_SCORE
+ description: Auto-resolve target metric (vitals + body-composition family + the PHQ-9 / GAD-7 / WHO-5 / SCI screenings).
+ BP resolves on BLOOD_PRESSURE_SYS (the SYS row is the 'a BP was measured' sentinel); a screening reminder
+ resolves on the server-written *_SCORE row when a check-in completes. Omit for a free-text Vorsorge that
resolves only on a manual satisfy or a matching lab result.
MeasurementReminderUpdate:
type: object
@@ -13917,6 +13930,8 @@ components:
- PAIN_NRS
- WAIST_CIRCUMFERENCE
- WAIST_TO_HEIGHT
+ - WHO5_SCORE
+ - SCI_SCORE
value:
type: number
unit:
@@ -14240,6 +14255,8 @@ components:
- PAIN_NRS
- WAIST_CIRCUMFERENCE
- WAIST_TO_HEIGHT
+ - WHO5_SCORE
+ - SCI_SCORE
additionalProperties:
type: array
items:
@@ -14323,6 +14340,8 @@ components:
- PAIN_NRS
- WAIST_CIRCUMFERENCE
- WAIST_TO_HEIGHT
+ - WHO5_SCORE
+ - SCI_SCORE
value:
type: number
description: Daily mean (spot metrics) or SUM (cumulative).
@@ -14427,6 +14446,8 @@ components:
- PAIN_NRS
- WAIST_CIRCUMFERENCE
- WAIST_TO_HEIGHT
+ - WHO5_SCORE
+ - SCI_SCORE
description: Per-type daily series, keyed by MeasurementType.
required:
- series
@@ -14666,6 +14687,8 @@ components:
- PAIN_NRS
- WAIST_CIRCUMFERENCE
- WAIST_TO_HEIGHT
+ - WHO5_SCORE
+ - SCI_SCORE
value:
type: number
unit:
@@ -25232,6 +25255,8 @@ components:
enum:
- PHQ9
- GAD7
+ - WHO5
+ - SCI
locale:
type: string
version:
diff --git a/messages/de.json b/messages/de.json
index 26a0cc13e..4f46565cf 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -599,7 +599,9 @@
"typeWaistCircumference": "Taillenumfang",
"typeWaistToHeight": "Taille-Größe-Verhältnis",
"unitScore": "Pkt.",
- "unitRatio": "Verhältnis"
+ "unitRatio": "Verhältnis",
+ "typeWho5Score": "WHO-5-Wert",
+ "typeSciScore": "SCI-Wert"
},
"mood": {
"title": "Stimmung",
@@ -7667,8 +7669,8 @@
},
"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.",
- "pageRhythmHint": "Beide Tests blicken auf die letzten zwei Wochen zurück — ein Check-in alle zwei bis vier Wochen ist deshalb ein guter Rhythmus.",
+ "pageDescription": "Vier validierte, freiwillige Selbsttests — PHQ-9 (Stimmung), GAD-7 (Sorgen), WHO-5 (Wohlbefinden) und SCI (Schlaf) — ergänzend zu deinem Stimmungstagebuch. Aussagekräftig ist der Verlauf über die Zeit, nicht ein einzelner Wert.",
+ "pageRhythmHint": "PHQ-9, GAD-7 und der Wohlbefindens-Check blicken auf die letzten zwei Wochen zurück, der Schlaf-Check auf den letzten Monat — ein Check-in alle zwei bis vier Wochen ist ein guter Rhythmus.",
"attributionLabel": "Quelle",
"choosePrompt": "Check-in auswählen",
"start": "Starten",
@@ -7677,14 +7679,17 @@
"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"
+ "gad7": "GAD-7 · Sorgen",
+ "who5": "WHO-5 · Wohlbefinden",
+ "sci": "SCI · Schlaf"
},
"instrumentSub": {
"phq9": "Depressions-Screening, 9 Fragen",
- "gad7": "Angst-Screening, 7 Fragen"
+ "gad7": "Angst-Screening, 7 Fragen",
+ "who5": "Wohlbefindens-Check, 5 Fragen",
+ "sci": "Schlaf-Check, 8 Fragen"
},
"options": {
"0": "Überhaupt nicht",
@@ -7719,16 +7724,23 @@
"mild": "Leicht",
"moderate": "Mittelgradig",
"severe": "Schwer"
+ },
+ "WHO5": {
+ "low": "Niedrigeres Wohlbefinden",
+ "good": "Gutes Wohlbefinden"
+ },
+ "SCI": {
+ "belowThreshold": "Möglicher Insomnie-Bereich",
+ "aboveThreshold": "Kein Insomnie-Hinweis"
}
},
"history": {
"title": "Dein Verlauf",
"empty": "Noch keine Check-ins. Dein Verlauf erscheint hier, sobald du einen abgeschlossen hast.",
"flaggedBadge": "Hilfe angezeigt",
- "tab": {
- "phq9": "PHQ-9",
- "gad7": "GAD-7"
- }
+ "chartTitle": "Punkteverlauf",
+ "totalLabel": "Punkte",
+ "bandLabel": "Einstufung"
},
"crisis": {
"title": "Du musst das nicht allein durchstehen",
@@ -7756,6 +7768,23 @@
"5": "Rastlosigkeit, sodass Stillsitzen schwerfällt",
"6": "Schnelle Verärgerung oder Gereiztheit",
"7": "Gefühl der Angst, als würde etwas Schlimmes passieren"
+ },
+ "who5": {
+ "1": "… war ich froh und guter Laune",
+ "2": "… habe ich mich ruhig und entspannt gefühlt",
+ "3": "… habe ich mich energisch und aktiv gefühlt",
+ "4": "… habe ich mich beim Aufwachen frisch und ausgeruht gefühlt",
+ "5": "… war mein Alltag voller Dinge, die mich interessieren"
+ },
+ "sci": {
+ "1": "… how long does it take you to fall asleep?",
+ "2": "… if you then wake up during the night … how long are you awake for in total? (add all the wakenings up)",
+ "3": "… how many nights a week do you have a problem with your sleep?",
+ "4": "… how would you rate your sleep quality?",
+ "5": "… affected your mood, energy, or relationships?",
+ "6": "… affected your concentration, productivity, or ability to stay awake?",
+ "7": "… troubled you in general?",
+ "8": "… how long have you had a problem with your sleep?"
}
},
"crisisResource": {
@@ -7783,14 +7812,79 @@
},
"instrumentDescription": {
"phq9": "Depressions-Screening mit 9 Fragen. Beziehe dich auf die letzten zwei Wochen.",
- "gad7": "Angst-Screening mit 7 Fragen. Beziehe dich auf die letzten zwei Wochen."
+ "gad7": "Angst-Screening mit 7 Fragen. Beziehe dich auf die letzten zwei Wochen.",
+ "who5": "Wohlbefindens-Check mit 5 Fragen. Beziehe dich auf die letzten zwei Wochen; höhere Werte bedeuten besseres Wohlbefinden.",
+ "sci": "Schlaf-Check mit 8 Fragen. Die meisten Fragen beziehen sich auf den letzten Monat; höhere Werte bedeuten besseren Schlaf."
},
"progress": "{current} / {total}",
"next": "Weiter",
"lastResult": "Letzter Test",
"lastScore": "Letztes Ergebnis",
"noResultYet": "Noch kein Check-in",
- "backToList": "Übersicht"
+ "backToList": "Übersicht",
+ "followUpHint": {
+ "phq9": "Dein Wert liegt in einem Bereich, in dem ein Gespräch mit einer Fachperson helfen kann. Das ist ein behutsamer Hinweis, keine Diagnose.",
+ "gad7": "Dein Wert liegt in einem Bereich, in dem ein Gespräch mit einer Fachperson helfen kann. Das ist ein behutsamer Hinweis, keine Diagnose.",
+ "who5": "Ein Wohlbefindens-Wert von 50 oder darunter kann einen genaueren Blick wert sein. Wenn du magst, schaut der PHQ-9-Check-in auf dieser Seite genauer auf die Stimmung. Das ist ein behutsamer Hinweis, keine Diagnose.",
+ "sci": "In der Studie zu diesem Fragebogen waren Werte von 16 oder darunter mit wahrscheinlicher Insomnie verbunden. Wenn schlechter Schlaf dich weiter belastet, kann ein Gespräch mit einer Fachperson helfen. Das ist keine Diagnose."
+ },
+ "validatedInEnglishNote": "Dieser Fragebogen ist in englischer Sprache validiert; die Fragen erscheinen im englischen Original.",
+ "who5Options": {
+ "5": "Die ganze Zeit",
+ "4": "Meistens",
+ "3": "Etwas mehr als die Hälfte der Zeit",
+ "2": "Etwas weniger als die Hälfte der Zeit",
+ "1": "Ab und zu",
+ "0": "Zu keinem Zeitpunkt"
+ },
+ "sciOptions": {
+ "duration": {
+ "4": "0–15 min",
+ "3": "16–30 min",
+ "2": "31–45 min",
+ "1": "46–60 min",
+ "0": "≥ 61 min"
+ },
+ "nights": {
+ "4": "0–1",
+ "3": "2",
+ "2": "3",
+ "1": "4",
+ "0": "5–7"
+ },
+ "quality": {
+ "4": "Very good",
+ "3": "Good",
+ "2": "Average",
+ "1": "Poor",
+ "0": "Very poor"
+ },
+ "impact": {
+ "4": "Not at all",
+ "3": "A little",
+ "2": "Somewhat",
+ "1": "Much",
+ "0": "Very much"
+ },
+ "problemDuration": {
+ "4": "I don't have a problem / < 1 month",
+ "3": "1–2 months",
+ "2": "3–6 months",
+ "1": "7–12 months",
+ "0": "> 1 year"
+ }
+ },
+ "stems": {
+ "who5": {
+ "period": "In den letzten zwei Wochen …"
+ },
+ "sci": {
+ "night": "Thinking about a typical night in the last month …",
+ "impact": "Thinking about the past month, to what extent has poor sleep …",
+ "finally": "Finally …"
+ }
+ },
+ "openDetail": "Details anzeigen"
},
"records": {
"disclaimer": "Das sind deine eigenen Angaben, keine ärztliche Diagnose. HealthLog stellt keine Diagnose, behandelt nicht und deutet nichts.",
diff --git a/messages/en.json b/messages/en.json
index 5d9a30932..3522fef3b 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -599,7 +599,9 @@
"typeWaistCircumference": "Waist circumference",
"typeWaistToHeight": "Waist-to-height ratio",
"unitScore": "pts",
- "unitRatio": "ratio"
+ "unitRatio": "ratio",
+ "typeWho5Score": "WHO-5 score",
+ "typeSciScore": "SCI score"
},
"mood": {
"title": "Mood",
@@ -7667,8 +7669,8 @@
},
"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.",
- "pageRhythmHint": "Both tests look back over the last two weeks, so a check-in every two to four weeks is a comfortable rhythm.",
+ "pageDescription": "Validated, opt-in self-checks — PHQ-9 (mood), GAD-7 (worry), WHO-5 (well-being) and SCI (sleep) — that sit beside your mood tracking. The value is the trend over time, not any single number.",
+ "pageRhythmHint": "PHQ-9, GAD-7 and the well-being check look back over the last two weeks; the sleep check asks about the past month. A check-in every two to four weeks is a comfortable rhythm.",
"attributionLabel": "Attribution",
"choosePrompt": "Choose a check-in",
"start": "Start",
@@ -7677,14 +7679,17 @@
"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"
+ "gad7": "GAD-7 · worry",
+ "who5": "WHO-5 · well-being",
+ "sci": "SCI · sleep"
},
"instrumentSub": {
"phq9": "Depression screen, 9 questions",
- "gad7": "Anxiety screen, 7 questions"
+ "gad7": "Anxiety screen, 7 questions",
+ "who5": "Well-being check, 5 questions",
+ "sci": "Sleep check, 8 questions"
},
"options": {
"0": "Not at all",
@@ -7719,16 +7724,23 @@
"mild": "Mild",
"moderate": "Moderate",
"severe": "Severe"
+ },
+ "WHO5": {
+ "low": "Lower well-being",
+ "good": "Good well-being"
+ },
+ "SCI": {
+ "belowThreshold": "Possible insomnia range",
+ "aboveThreshold": "No insomnia indication"
}
},
"history": {
"title": "Your history",
"empty": "No check-ins yet. Your trend will appear here once you complete one.",
"flaggedBadge": "Support shown",
- "tab": {
- "phq9": "PHQ-9",
- "gad7": "GAD-7"
- }
+ "chartTitle": "Score trend",
+ "totalLabel": "Score",
+ "bandLabel": "Category"
},
"crisis": {
"title": "You don't have to face this alone",
@@ -7756,6 +7768,23 @@
"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"
+ },
+ "who5": {
+ "1": "I have felt cheerful and in good spirits",
+ "2": "I have felt calm and relaxed",
+ "3": "I have felt active and vigorous",
+ "4": "I woke up feeling fresh and rested",
+ "5": "My daily life has been filled with things that interest me"
+ },
+ "sci": {
+ "1": "… how long does it take you to fall asleep?",
+ "2": "… if you then wake up during the night … how long are you awake for in total? (add all the wakenings up)",
+ "3": "… how many nights a week do you have a problem with your sleep?",
+ "4": "… how would you rate your sleep quality?",
+ "5": "… affected your mood, energy, or relationships?",
+ "6": "… affected your concentration, productivity, or ability to stay awake?",
+ "7": "… troubled you in general?",
+ "8": "… how long have you had a problem with your sleep?"
}
},
"crisisResource": {
@@ -7783,14 +7812,79 @@
},
"instrumentDescription": {
"phq9": "9-question depression screen. Answer for the last two weeks.",
- "gad7": "7-question anxiety screen. Answer for the last two weeks."
+ "gad7": "7-question anxiety screen. Answer for the last two weeks.",
+ "who5": "5-question well-being check. Answer for the last two weeks; higher numbers mean better well-being.",
+ "sci": "8-question sleep check. Most questions ask about the past month; higher scores mean better sleep."
},
"progress": "{current} / {total}",
"next": "Next",
"lastResult": "Last test",
"lastScore": "Last result",
"noResultYet": "No check-in yet",
- "backToList": "Overview"
+ "backToList": "Overview",
+ "followUpHint": {
+ "phq9": "Your score is in a range where talking to a health professional may help. This is a gentle suggestion, not a diagnosis.",
+ "gad7": "Your score is in a range where talking to a health professional may help. This is a gentle suggestion, not a diagnosis.",
+ "who5": "A well-being score of 50 or below can be worth a closer look. If you like, the PHQ-9 check-in on this page looks at mood in more depth. This is a gentle suggestion, not a diagnosis.",
+ "sci": "In the study behind this questionnaire, scores of 16 or below were associated with probable insomnia. If poor sleep keeps troubling you, talking to a health professional may help. This is not a diagnosis."
+ },
+ "validatedInEnglishNote": "This questionnaire is validated in English; the questions are shown in the original English wording.",
+ "who5Options": {
+ "5": "All of the time",
+ "4": "Most of the time",
+ "3": "More than half of the time",
+ "2": "Less than half of the time",
+ "1": "Some of the time",
+ "0": "At no time"
+ },
+ "sciOptions": {
+ "duration": {
+ "4": "0–15 min",
+ "3": "16–30 min",
+ "2": "31–45 min",
+ "1": "46–60 min",
+ "0": "≥ 61 min"
+ },
+ "nights": {
+ "4": "0–1",
+ "3": "2",
+ "2": "3",
+ "1": "4",
+ "0": "5–7"
+ },
+ "quality": {
+ "4": "Very good",
+ "3": "Good",
+ "2": "Average",
+ "1": "Poor",
+ "0": "Very poor"
+ },
+ "impact": {
+ "4": "Not at all",
+ "3": "A little",
+ "2": "Somewhat",
+ "1": "Much",
+ "0": "Very much"
+ },
+ "problemDuration": {
+ "4": "I don't have a problem / < 1 month",
+ "3": "1–2 months",
+ "2": "3–6 months",
+ "1": "7–12 months",
+ "0": "> 1 year"
+ }
+ },
+ "stems": {
+ "who5": {
+ "period": "Over the last two weeks …"
+ },
+ "sci": {
+ "night": "Thinking about a typical night in the last month …",
+ "impact": "Thinking about the past month, to what extent has poor sleep …",
+ "finally": "Finally …"
+ }
+ },
+ "openDetail": "Show details"
},
"records": {
"disclaimer": "These are your own records, not a medical diagnosis. HealthLog does not diagnose, treat, or interpret them.",
diff --git a/messages/es.json b/messages/es.json
index 7f44c1b69..65b78e5e9 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -599,7 +599,9 @@
"typeWaistCircumference": "Perímetro de cintura",
"typeWaistToHeight": "Índice cintura-altura",
"unitScore": "pts",
- "unitRatio": "ratio"
+ "unitRatio": "ratio",
+ "typeWho5Score": "Puntuación WHO-5",
+ "typeSciScore": "Puntuación SCI"
},
"mood": {
"title": "Estado de ánimo",
@@ -7667,8 +7669,8 @@
},
"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.",
- "pageRhythmHint": "Ambos tests se refieren a las últimas dos semanas, por lo que un check-in cada dos a cuatro semanas es un buen ritmo.",
+ "pageDescription": "Cuatro autoevaluaciones validadas y opcionales — PHQ-9 (estado de ánimo), GAD-7 (preocupación), WHO-5 (bienestar) y SCI (sueño) — junto a tu registro de ánimo. Lo valioso es la tendencia en el tiempo, no un único número.",
+ "pageRhythmHint": "El PHQ-9, el GAD-7 y el test de bienestar se refieren a las últimas dos semanas; el test de sueño pregunta por el último mes. Un check-in cada dos a cuatro semanas es un buen ritmo.",
"attributionLabel": "Fuente",
"choosePrompt": "Elige una revisión",
"start": "Empezar",
@@ -7677,14 +7679,17 @@
"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"
+ "gad7": "GAD-7 · preocupación",
+ "who5": "WHO-5 · bienestar",
+ "sci": "SCI · sueño"
},
"instrumentSub": {
"phq9": "Cribado de depresión, 9 preguntas",
- "gad7": "Cribado de ansiedad, 7 preguntas"
+ "gad7": "Cribado de ansiedad, 7 preguntas",
+ "who5": "Test de bienestar, 5 preguntas",
+ "sci": "Test de sueño, 8 preguntas"
},
"options": {
"0": "Nunca",
@@ -7719,16 +7724,23 @@
"mild": "Leve",
"moderate": "Moderada",
"severe": "Grave"
+ },
+ "WHO5": {
+ "low": "Bienestar más bajo",
+ "good": "Buen bienestar"
+ },
+ "SCI": {
+ "belowThreshold": "Rango de posible insomnio",
+ "aboveThreshold": "Sin indicios de insomnio"
}
},
"history": {
"title": "Tu historial",
"empty": "Aún no hay revisiones. Tu tendencia aparecerá aquí cuando completes una.",
"flaggedBadge": "Apoyo mostrado",
- "tab": {
- "phq9": "PHQ-9",
- "gad7": "GAD-7"
- }
+ "chartTitle": "Evolución de la puntuación",
+ "totalLabel": "Puntuación",
+ "bandLabel": "Categoría"
},
"crisis": {
"title": "No tienes que afrontar esto a solas",
@@ -7756,6 +7768,23 @@
"5": "Estar tan inquieto/a que es difícil quedarse sentado/a tranquilamente",
"6": "Molestarse o irritarse fácilmente",
"7": "Sentir miedo como si algo terrible fuera a suceder"
+ },
+ "who5": {
+ "1": "Me he sentido alegre y de buen humor",
+ "2": "Me he sentido tranquilo y relajado",
+ "3": "Me he sentido activo y enérgico",
+ "4": "Me he despertado fresco y descansado",
+ "5": "Mi vida cotidiana ha estado llena de cosas que me interesan"
+ },
+ "sci": {
+ "1": "… how long does it take you to fall asleep?",
+ "2": "… if you then wake up during the night … how long are you awake for in total? (add all the wakenings up)",
+ "3": "… how many nights a week do you have a problem with your sleep?",
+ "4": "… how would you rate your sleep quality?",
+ "5": "… affected your mood, energy, or relationships?",
+ "6": "… affected your concentration, productivity, or ability to stay awake?",
+ "7": "… troubled you in general?",
+ "8": "… how long have you had a problem with your sleep?"
}
},
"crisisResource": {
@@ -7783,14 +7812,79 @@
},
"instrumentDescription": {
"phq9": "Cribado de depresión de 9 preguntas. Responde pensando en las últimas dos semanas.",
- "gad7": "Cribado de ansiedad de 7 preguntas. Responde pensando en las últimas dos semanas."
+ "gad7": "Cribado de ansiedad de 7 preguntas. Responde pensando en las últimas dos semanas.",
+ "who5": "Test de bienestar de 5 preguntas. Responde pensando en las últimas dos semanas; cifras mayores significan mayor bienestar.",
+ "sci": "Test de sueño de 8 preguntas. La mayoría se refiere al último mes; puntuaciones mayores significan mejor sueño."
},
"progress": "{current} / {total}",
"next": "Siguiente",
"lastResult": "Última prueba",
"lastScore": "Último resultado",
"noResultYet": "Aún sin check-in",
- "backToList": "Vista general"
+ "backToList": "Vista general",
+ "followUpHint": {
+ "phq9": "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.",
+ "gad7": "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.",
+ "who5": "Una puntuación de bienestar de 50 o menos puede merecer una mirada más atenta. Si quieres, el PHQ-9 de esta página explora el estado de ánimo con más detalle. Es una sugerencia amable, no un diagnóstico.",
+ "sci": "En el estudio de este cuestionario, puntuaciones de 16 o menos se asociaron con insomnio probable. Si dormir mal te sigue afectando, hablar con un profesional de salud puede ayudar. No es un diagnóstico."
+ },
+ "validatedInEnglishNote": "Este cuestionario está validado en inglés; las preguntas se muestran en su versión original en inglés.",
+ "who5Options": {
+ "5": "Todo el tiempo",
+ "4": "La mayor parte del tiempo",
+ "3": "Más de la mitad del tiempo",
+ "2": "Menos de la mitad del tiempo",
+ "1": "De vez en cuando",
+ "0": "Nunca"
+ },
+ "sciOptions": {
+ "duration": {
+ "4": "0–15 min",
+ "3": "16–30 min",
+ "2": "31–45 min",
+ "1": "46–60 min",
+ "0": "≥ 61 min"
+ },
+ "nights": {
+ "4": "0–1",
+ "3": "2",
+ "2": "3",
+ "1": "4",
+ "0": "5–7"
+ },
+ "quality": {
+ "4": "Very good",
+ "3": "Good",
+ "2": "Average",
+ "1": "Poor",
+ "0": "Very poor"
+ },
+ "impact": {
+ "4": "Not at all",
+ "3": "A little",
+ "2": "Somewhat",
+ "1": "Much",
+ "0": "Very much"
+ },
+ "problemDuration": {
+ "4": "I don't have a problem / < 1 month",
+ "3": "1–2 months",
+ "2": "3–6 months",
+ "1": "7–12 months",
+ "0": "> 1 year"
+ }
+ },
+ "stems": {
+ "who5": {
+ "period": "Durante las últimas dos semanas:"
+ },
+ "sci": {
+ "night": "Thinking about a typical night in the last month …",
+ "impact": "Thinking about the past month, to what extent has poor sleep …",
+ "finally": "Finally …"
+ }
+ },
+ "openDetail": "Ver detalles"
},
"records": {
"disclaimer": "Son tus propios registros, no un diagnóstico médico. HealthLog no diagnostica, no trata ni los interpreta.",
diff --git a/messages/fr.json b/messages/fr.json
index c150ab3d4..722bbcb76 100644
--- a/messages/fr.json
+++ b/messages/fr.json
@@ -599,7 +599,9 @@
"typeWaistCircumference": "Tour de taille",
"typeWaistToHeight": "Rapport taille-taille",
"unitScore": "pts",
- "unitRatio": "ratio"
+ "unitRatio": "ratio",
+ "typeWho5Score": "Score WHO-5",
+ "typeSciScore": "Score SCI"
},
"mood": {
"title": "Humeur",
@@ -7667,8 +7669,8 @@
},
"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é.",
- "pageRhythmHint": "Les deux tests portent sur les deux dernières semaines — un check-in toutes les deux à quatre semaines est donc un bon rythme.",
+ "pageDescription": "Quatre auto-évaluations validées et facultatives — PHQ-9 (humeur), GAD-7 (inquiétude), WHO-5 (bien-être) et SCI (sommeil) — en complément de ton suivi de l'humeur. Ce qui compte, c'est la tendance dans le temps, pas un chiffre isolé.",
+ "pageRhythmHint": "Le PHQ-9, le GAD-7 et le test de bien-être portent sur les deux dernières semaines ; le test de sommeil porte sur le dernier mois. Un check-in toutes les deux à quatre semaines est un bon rythme.",
"attributionLabel": "Source",
"choosePrompt": "Choisis un bilan",
"start": "Commencer",
@@ -7677,14 +7679,17 @@
"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"
+ "gad7": "GAD-7 · inquiétude",
+ "who5": "WHO-5 · bien-être",
+ "sci": "SCI · sommeil"
},
"instrumentSub": {
"phq9": "Dépistage de la dépression, 9 questions",
- "gad7": "Dépistage de l'anxiété, 7 questions"
+ "gad7": "Dépistage de l'anxiété, 7 questions",
+ "who5": "Test de bien-être, 5 questions",
+ "sci": "Test de sommeil, 8 questions"
},
"options": {
"0": "Jamais",
@@ -7719,16 +7724,23 @@
"mild": "Légère",
"moderate": "Modérée",
"severe": "Sévère"
+ },
+ "WHO5": {
+ "low": "Bien-être plus faible",
+ "good": "Bon bien-être"
+ },
+ "SCI": {
+ "belowThreshold": "Zone d'insomnie possible",
+ "aboveThreshold": "Pas d'indice d'insomnie"
}
},
"history": {
"title": "Ton historique",
"empty": "Aucun bilan pour le moment. Ta tendance apparaîtra ici dès que tu en auras terminé un.",
"flaggedBadge": "Aide affichée",
- "tab": {
- "phq9": "PHQ-9",
- "gad7": "GAD-7"
- }
+ "chartTitle": "Évolution du score",
+ "totalLabel": "Score",
+ "bandLabel": "Catégorie"
},
"crisis": {
"title": "Tu n'as pas à affronter cela seul(e)",
@@ -7756,6 +7768,23 @@
"5": "Être si agité(e) qu'il est difficile de rester tranquille",
"6": "Devenir facilement contrarié(e) ou irritable",
"7": "Avoir peur que quelque chose d'épouvantable puisse arriver"
+ },
+ "who5": {
+ "1": "Je me suis senti(e) bien et de bonne humeur",
+ "2": "Je me suis senti(e) calme et tranquille",
+ "3": "Je me suis senti(e) plein(e) d'énergie et vigoureux(se)",
+ "4": "Je me suis réveillé(e) en me sentant frais(che) et dispos(e)",
+ "5": "Ma vie quotidienne a été remplie de choses intéressantes"
+ },
+ "sci": {
+ "1": "… how long does it take you to fall asleep?",
+ "2": "… if you then wake up during the night … how long are you awake for in total? (add all the wakenings up)",
+ "3": "… how many nights a week do you have a problem with your sleep?",
+ "4": "… how would you rate your sleep quality?",
+ "5": "… affected your mood, energy, or relationships?",
+ "6": "… affected your concentration, productivity, or ability to stay awake?",
+ "7": "… troubled you in general?",
+ "8": "… how long have you had a problem with your sleep?"
}
},
"crisisResource": {
@@ -7783,14 +7812,79 @@
},
"instrumentDescription": {
"phq9": "Dépistage de la dépression en 9 questions. Répondez en pensant aux deux dernières semaines.",
- "gad7": "Dépistage de l'anxiété en 7 questions. Répondez en pensant aux deux dernières semaines."
+ "gad7": "Dépistage de l'anxiété en 7 questions. Répondez en pensant aux deux dernières semaines.",
+ "who5": "Test de bien-être en 5 questions. Répondez en pensant aux deux dernières semaines ; des chiffres plus élevés signifient un meilleur bien-être.",
+ "sci": "Test de sommeil en 8 questions. La plupart des questions portent sur le dernier mois ; des scores plus élevés signifient un meilleur sommeil."
},
"progress": "{current} / {total}",
"next": "Suivant",
"lastResult": "Dernier test",
"lastScore": "Dernier résultat",
"noResultYet": "Pas encore de check-in",
- "backToList": "Vue d'ensemble"
+ "backToList": "Vue d'ensemble",
+ "followUpHint": {
+ "phq9": "Ton score se situe dans une plage où parler à un professionnel de santé peut aider. C'est une suggestion bienveillante, pas un diagnostic.",
+ "gad7": "Ton score se situe dans une plage où parler à un professionnel de santé peut aider. C'est une suggestion bienveillante, pas un diagnostic.",
+ "who5": "Un score de bien-être de 50 ou moins peut mériter un regard plus attentif. Si tu le souhaites, le PHQ-9 sur cette page explore l'humeur plus en détail. C'est une suggestion bienveillante, pas un diagnostic.",
+ "sci": "Dans l'étude de ce questionnaire, des scores de 16 ou moins étaient associés à une insomnie probable. Si un mauvais sommeil continue de te peser, parler à un professionnel de santé peut aider. Ce n'est pas un diagnostic."
+ },
+ "validatedInEnglishNote": "Ce questionnaire est validé en anglais ; les questions sont affichées dans leur version originale anglaise.",
+ "who5Options": {
+ "5": "Tout le temps",
+ "4": "La plupart du temps",
+ "3": "Plus de la moitié du temps",
+ "2": "Moins de la moitié du temps",
+ "1": "De temps en temps",
+ "0": "Jamais"
+ },
+ "sciOptions": {
+ "duration": {
+ "4": "0–15 min",
+ "3": "16–30 min",
+ "2": "31–45 min",
+ "1": "46–60 min",
+ "0": "≥ 61 min"
+ },
+ "nights": {
+ "4": "0–1",
+ "3": "2",
+ "2": "3",
+ "1": "4",
+ "0": "5–7"
+ },
+ "quality": {
+ "4": "Very good",
+ "3": "Good",
+ "2": "Average",
+ "1": "Poor",
+ "0": "Very poor"
+ },
+ "impact": {
+ "4": "Not at all",
+ "3": "A little",
+ "2": "Somewhat",
+ "1": "Much",
+ "0": "Very much"
+ },
+ "problemDuration": {
+ "4": "I don't have a problem / < 1 month",
+ "3": "1–2 months",
+ "2": "3–6 months",
+ "1": "7–12 months",
+ "0": "> 1 year"
+ }
+ },
+ "stems": {
+ "who5": {
+ "period": "Au cours des deux dernières semaines"
+ },
+ "sci": {
+ "night": "Thinking about a typical night in the last month …",
+ "impact": "Thinking about the past month, to what extent has poor sleep …",
+ "finally": "Finally …"
+ }
+ },
+ "openDetail": "Voir les détails"
},
"records": {
"disclaimer": "Ce sont vos propres informations, pas un diagnostic médical. HealthLog ne diagnostique pas, ne traite pas et ne les interprète pas.",
diff --git a/messages/it.json b/messages/it.json
index 61a2d52e7..a9b8176bf 100644
--- a/messages/it.json
+++ b/messages/it.json
@@ -599,7 +599,9 @@
"typeWaistCircumference": "Circonferenza vita",
"typeWaistToHeight": "Rapporto vita-altezza",
"unitScore": "pti",
- "unitRatio": "rapporto"
+ "unitRatio": "rapporto",
+ "typeWho5Score": "Punteggio WHO-5",
+ "typeSciScore": "Punteggio SCI"
},
"mood": {
"title": "Umore",
@@ -7667,8 +7669,8 @@
},
"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.",
- "pageRhythmHint": "Entrambi i test riguardano le ultime due settimane, quindi un check-in ogni due-quattro settimane è un buon ritmo.",
+ "pageDescription": "Quattro autovalutazioni validate e facoltative — PHQ-9 (umore), GAD-7 (preoccupazione), WHO-5 (benessere) e SCI (sonno) — accanto al tuo diario dell'umore. Ciò che conta è l'andamento nel tempo, non un singolo numero.",
+ "pageRhythmHint": "PHQ-9, GAD-7 e il test sul benessere riguardano le ultime due settimane; il test sul sonno chiede dell'ultimo mese. Un check-in ogni due-quattro settimane è un buon ritmo.",
"attributionLabel": "Fonte",
"choosePrompt": "Scegli un check-in",
"start": "Inizia",
@@ -7677,14 +7679,17 @@
"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"
+ "gad7": "GAD-7 · preoccupazione",
+ "who5": "WHO-5 · benessere",
+ "sci": "SCI · sonno"
},
"instrumentSub": {
"phq9": "Screening della depressione, 9 domande",
- "gad7": "Screening dell'ansia, 7 domande"
+ "gad7": "Screening dell'ansia, 7 domande",
+ "who5": "Test sul benessere, 5 domande",
+ "sci": "Test sul sonno, 8 domande"
},
"options": {
"0": "Per niente",
@@ -7719,16 +7724,23 @@
"mild": "Lieve",
"moderate": "Moderata",
"severe": "Grave"
+ },
+ "WHO5": {
+ "low": "Benessere più basso",
+ "good": "Buon benessere"
+ },
+ "SCI": {
+ "belowThreshold": "Fascia di possibile insonnia",
+ "aboveThreshold": "Nessun indizio di insonnia"
}
},
"history": {
"title": "La tua cronologia",
"empty": "Ancora nessun check-in. Il tuo andamento apparirà qui dopo che ne avrai completato uno.",
"flaggedBadge": "Supporto mostrato",
- "tab": {
- "phq9": "PHQ-9",
- "gad7": "GAD-7"
- }
+ "chartTitle": "Andamento del punteggio",
+ "totalLabel": "Punteggio",
+ "bandLabel": "Categoria"
},
"crisis": {
"title": "Non devi affrontare tutto questo da solo/a",
@@ -7756,6 +7768,23 @@
"5": "Essere così irrequieto/a da far fatica a stare seduto/a fermo/a",
"6": "Infastidirsi o irritarsi facilmente",
"7": "Avere paura come se potesse accadere qualcosa di terribile"
+ },
+ "who5": {
+ "1": "Mi sono sentito/a allegro/a e di buon umore",
+ "2": "Mi sono sentito/a calmo/a e rilassato/a",
+ "3": "Mi sono sentito/a attivo/a ed energico/a",
+ "4": "Mi sono svegliato/a sentendomi fresco/a e riposato/a",
+ "5": "La mia vita di tutti i giorni è stata piena di cose che mi interessano"
+ },
+ "sci": {
+ "1": "… how long does it take you to fall asleep?",
+ "2": "… if you then wake up during the night … how long are you awake for in total? (add all the wakenings up)",
+ "3": "… how many nights a week do you have a problem with your sleep?",
+ "4": "… how would you rate your sleep quality?",
+ "5": "… affected your mood, energy, or relationships?",
+ "6": "… affected your concentration, productivity, or ability to stay awake?",
+ "7": "… troubled you in general?",
+ "8": "… how long have you had a problem with your sleep?"
}
},
"crisisResource": {
@@ -7783,14 +7812,79 @@
},
"instrumentDescription": {
"phq9": "Screening della depressione in 9 domande. Rispondi pensando alle ultime due settimane.",
- "gad7": "Screening dell'ansia in 7 domande. Rispondi pensando alle ultime due settimane."
+ "gad7": "Screening dell'ansia in 7 domande. Rispondi pensando alle ultime due settimane.",
+ "who5": "Test sul benessere in 5 domande. Rispondi pensando alle ultime due settimane; numeri più alti indicano un maggior benessere.",
+ "sci": "Test sul sonno in 8 domande. La maggior parte riguarda l'ultimo mese; punteggi più alti indicano un sonno migliore."
},
"progress": "{current} / {total}",
"next": "Avanti",
"lastResult": "Ultimo test",
"lastScore": "Ultimo risultato",
"noResultYet": "Ancora nessun check-in",
- "backToList": "Panoramica"
+ "backToList": "Panoramica",
+ "followUpHint": {
+ "phq9": "Il tuo punteggio è in un intervallo in cui parlare con un professionista sanitario può aiutare. È un suggerimento gentile, non una diagnosi.",
+ "gad7": "Il tuo punteggio è in un intervallo in cui parlare con un professionista sanitario può aiutare. È un suggerimento gentile, non una diagnosi.",
+ "who5": "Un punteggio di benessere pari o inferiore a 50 può meritare uno sguardo più attento. Se vuoi, il PHQ-9 in questa pagina esplora l'umore più in dettaglio. È un suggerimento gentile, non una diagnosi.",
+ "sci": "Nello studio di questo questionario, punteggi pari o inferiori a 16 erano associati a insonnia probabile. Se dormire male continua a pesarti, parlare con un professionista sanitario può aiutare. Non è una diagnosi."
+ },
+ "validatedInEnglishNote": "Questo questionario è validato in inglese; le domande sono mostrate nella versione originale inglese.",
+ "who5Options": {
+ "5": "Sempre",
+ "4": "La maggior parte del tempo",
+ "3": "Più della metà del tempo",
+ "2": "Meno della metà del tempo",
+ "1": "A volte",
+ "0": "Mai"
+ },
+ "sciOptions": {
+ "duration": {
+ "4": "0–15 min",
+ "3": "16–30 min",
+ "2": "31–45 min",
+ "1": "46–60 min",
+ "0": "≥ 61 min"
+ },
+ "nights": {
+ "4": "0–1",
+ "3": "2",
+ "2": "3",
+ "1": "4",
+ "0": "5–7"
+ },
+ "quality": {
+ "4": "Very good",
+ "3": "Good",
+ "2": "Average",
+ "1": "Poor",
+ "0": "Very poor"
+ },
+ "impact": {
+ "4": "Not at all",
+ "3": "A little",
+ "2": "Somewhat",
+ "1": "Much",
+ "0": "Very much"
+ },
+ "problemDuration": {
+ "4": "I don't have a problem / < 1 month",
+ "3": "1–2 months",
+ "2": "3–6 months",
+ "1": "7–12 months",
+ "0": "> 1 year"
+ }
+ },
+ "stems": {
+ "who5": {
+ "period": "Nelle ultime due settimane"
+ },
+ "sci": {
+ "night": "Thinking about a typical night in the last month …",
+ "impact": "Thinking about the past month, to what extent has poor sleep …",
+ "finally": "Finally …"
+ }
+ },
+ "openDetail": "Mostra dettagli"
},
"records": {
"disclaimer": "Sono i tuoi dati, non una diagnosi medica. HealthLog non diagnostica, non cura e non li interpreta.",
diff --git a/messages/pl.json b/messages/pl.json
index 218e6eea5..51ac363d4 100644
--- a/messages/pl.json
+++ b/messages/pl.json
@@ -599,7 +599,9 @@
"typeWaistCircumference": "Obwód talii",
"typeWaistToHeight": "Stosunek talii do wzrostu",
"unitScore": "pkt",
- "unitRatio": "stosunek"
+ "unitRatio": "stosunek",
+ "typeWho5Score": "Wynik WHO-5",
+ "typeSciScore": "Wynik SCI"
},
"mood": {
"title": "Nastrój",
@@ -7667,8 +7669,8 @@
},
"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.",
- "pageRhythmHint": "Oba testy dotyczą ostatnich dwóch tygodni, dlatego check-in co dwa–cztery tygodnie to dobry rytm.",
+ "pageDescription": "Cztery zwalidowane, dobrowolne testy — PHQ-9 (nastrój), GAD-7 (lęk), WHO-5 (samopoczucie) i SCI (sen) — obok Twojego dziennika nastroju. Liczy się trend w czasie, a nie pojedynczy wynik.",
+ "pageRhythmHint": "PHQ-9, GAD-7 i test samopoczucia dotyczą ostatnich dwóch tygodni; test snu pyta o ostatni miesiąc. Check-in co dwa–cztery tygodnie to dobry rytm.",
"attributionLabel": "Źródło",
"choosePrompt": "Wybierz sprawdzenie",
"start": "Rozpocznij",
@@ -7677,14 +7679,17 @@
"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"
+ "gad7": "GAD-7 · lęk",
+ "who5": "WHO-5 · samopoczucie",
+ "sci": "SCI · sen"
},
"instrumentSub": {
"phq9": "Przesiew depresji, 9 pytań",
- "gad7": "Przesiew lęku, 7 pytań"
+ "gad7": "Przesiew lęku, 7 pytań",
+ "who5": "Test samopoczucia, 5 pytań",
+ "sci": "Test snu, 8 pytań"
},
"options": {
"0": "Wcale",
@@ -7719,16 +7724,23 @@
"mild": "Łagodny",
"moderate": "Umiarkowany",
"severe": "Ciężki"
+ },
+ "WHO5": {
+ "low": "Niższe samopoczucie",
+ "good": "Dobre samopoczucie"
+ },
+ "SCI": {
+ "belowThreshold": "Zakres możliwej bezsenności",
+ "aboveThreshold": "Brak wskazań na bezsenność"
}
},
"history": {
"title": "Twoja historia",
"empty": "Brak sprawdzeń. Twój trend pojawi się tutaj po ukończeniu pierwszego.",
"flaggedBadge": "Pokazano wsparcie",
- "tab": {
- "phq9": "PHQ-9",
- "gad7": "GAD-7"
- }
+ "chartTitle": "Przebieg wyniku",
+ "totalLabel": "Wynik",
+ "bandLabel": "Kategoria"
},
"crisis": {
"title": "Nie musisz mierzyć się z tym sam(a)",
@@ -7756,6 +7768,23 @@
"5": "Niepokój tak duży, że trudno usiedzieć w miejscu",
"6": "Łatwe wpadanie w rozdrażnienie lub irytację",
"7": "Odczuwanie lęku, jakby miało się zdarzyć coś strasznego"
+ },
+ "who5": {
+ "1": "Czułam się wesoła/Czułem się wesoły i w dobrym nastroju",
+ "2": "Czułam się spokojna i odprężona/Czułem się spokojny i odprężony",
+ "3": "Czułam się aktywna i energiczna/Czułem się aktywny i energiczny",
+ "4": "Budziłam się z uczuciem świeżości i wypoczęta/Budziłem się z uczuciem świeżości i wypoczęty",
+ "5": "Moje życie codzienne było wypełnione interesującymi mnie sprawami"
+ },
+ "sci": {
+ "1": "… how long does it take you to fall asleep?",
+ "2": "… if you then wake up during the night … how long are you awake for in total? (add all the wakenings up)",
+ "3": "… how many nights a week do you have a problem with your sleep?",
+ "4": "… how would you rate your sleep quality?",
+ "5": "… affected your mood, energy, or relationships?",
+ "6": "… affected your concentration, productivity, or ability to stay awake?",
+ "7": "… troubled you in general?",
+ "8": "… how long have you had a problem with your sleep?"
}
},
"crisisResource": {
@@ -7783,14 +7812,79 @@
},
"instrumentDescription": {
"phq9": "Przesiew w kierunku depresji, 9 pytań. Odpowiadaj, myśląc o ostatnich dwóch tygodniach.",
- "gad7": "Przesiew w kierunku lęku, 7 pytań. Odpowiadaj, myśląc o ostatnich dwóch tygodniach."
+ "gad7": "Przesiew w kierunku lęku, 7 pytań. Odpowiadaj, myśląc o ostatnich dwóch tygodniach.",
+ "who5": "Test samopoczucia, 5 pytań. Odpowiadaj, myśląc o ostatnich dwóch tygodniach; wyższe cyfry oznaczają lepsze samopoczucie.",
+ "sci": "Test snu, 8 pytań. Większość pytań dotyczy ostatniego miesiąca; wyższe wyniki oznaczają lepszy sen."
},
"progress": "{current} / {total}",
"next": "Dalej",
"lastResult": "Ostatni test",
"lastScore": "Ostatni wynik",
"noResultYet": "Brak check-inów",
- "backToList": "Przegląd"
+ "backToList": "Przegląd",
+ "followUpHint": {
+ "phq9": "Twój wynik mieści się w zakresie, w którym rozmowa ze specjalistą może pomóc. To życzliwa sugestia, a nie diagnoza.",
+ "gad7": "Twój wynik mieści się w zakresie, w którym rozmowa ze specjalistą może pomóc. To życzliwa sugestia, a nie diagnoza.",
+ "who5": "Wynik samopoczucia 50 lub niższy może być wart bliższego spojrzenia. Jeśli chcesz, PHQ-9 na tej stronie przygląda się nastrojowi dokładniej. To życzliwa sugestia, a nie diagnoza.",
+ "sci": "W badaniu tego kwestionariusza wyniki 16 lub niższe wiązały się z prawdopodobną bezsennością. Jeśli zły sen nadal Ci doskwiera, rozmowa ze specjalistą może pomóc. To nie jest diagnoza."
+ },
+ "validatedInEnglishNote": "Ten kwestionariusz jest zwalidowany w języku angielskim; pytania są wyświetlane w angielskim oryginale.",
+ "who5Options": {
+ "5": "Cały czas",
+ "4": "Prawie cały czas",
+ "3": "Więcej niż połowę czasu",
+ "2": "Mniej niż połowę czasu",
+ "1": "Od czasu do czasu",
+ "0": "Nigdy"
+ },
+ "sciOptions": {
+ "duration": {
+ "4": "0–15 min",
+ "3": "16–30 min",
+ "2": "31–45 min",
+ "1": "46–60 min",
+ "0": "≥ 61 min"
+ },
+ "nights": {
+ "4": "0–1",
+ "3": "2",
+ "2": "3",
+ "1": "4",
+ "0": "5–7"
+ },
+ "quality": {
+ "4": "Very good",
+ "3": "Good",
+ "2": "Average",
+ "1": "Poor",
+ "0": "Very poor"
+ },
+ "impact": {
+ "4": "Not at all",
+ "3": "A little",
+ "2": "Somewhat",
+ "1": "Much",
+ "0": "Very much"
+ },
+ "problemDuration": {
+ "4": "I don't have a problem / < 1 month",
+ "3": "1–2 months",
+ "2": "3–6 months",
+ "1": "7–12 months",
+ "0": "> 1 year"
+ }
+ },
+ "stems": {
+ "who5": {
+ "period": "W ciągu ostatnich dwóch tygodni"
+ },
+ "sci": {
+ "night": "Thinking about a typical night in the last month …",
+ "impact": "Thinking about the past month, to what extent has poor sleep …",
+ "finally": "Finally …"
+ }
+ },
+ "openDetail": "Pokaż szczegóły"
},
"records": {
"disclaimer": "To Twoje własne wpisy, a nie diagnoza lekarska. HealthLog nie diagnozuje, nie leczy i ich nie interpretuje.",
diff --git a/prisma/migrations/0226_v1279_screening_scores/migration.sql b/prisma/migrations/0226_v1279_screening_scores/migration.sql
new file mode 100644
index 000000000..ab9f6f77b
--- /dev/null
+++ b/prisma/migrations/0226_v1279_screening_scores/migration.sql
@@ -0,0 +1,15 @@
+-- v1.27.9 — screening-score enum values for the two new self-report
+-- instruments: the WHO-5 well-being index (percentage 0–100, raw sum × 4)
+-- and the Sleep Condition Indicator (total 0–32, higher = better sleep).
+--
+-- Same contract as PHQ9_SCORE / GAD7_SCORE (0213/0225 lineage): the value is
+-- server-derived from a completed MentalHealthAssessment and written as a
+-- `COMPUTED`-source Measurement row, so clients can never forge a trend point
+-- through the measurement write surfaces. Additive, idempotent.
+ALTER TYPE "measurement_type" ADD VALUE IF NOT EXISTS 'WHO5_SCORE';
+ALTER TYPE "measurement_type" ADD VALUE IF NOT EXISTS 'SCI_SCORE';
+
+-- The assessment discriminator learns the two new instruments (0213 created
+-- it with PHQ9/GAD7 only).
+ALTER TYPE "assessment_instrument" ADD VALUE IF NOT EXISTS 'WHO5';
+ALTER TYPE "assessment_instrument" ADD VALUE IF NOT EXISTS 'SCI';
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 4c25019fb..a4f69bf9c 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1284,17 +1284,26 @@ enum MeasurementType {
// waist-to-height ratio (WHtR; keep waist < half your height ⇒ < 0.5).
WAIST_CIRCUMFERENCE // cm — waist circumference (WHO bands; ethnicity-aware at display).
WAIST_TO_HEIGHT // ratio — waist-to-height ratio (WHtR ≥ 0.5 = increased risk).
+ // ── v1.27.9 — screening scores (WHO-5 well-being + Sleep Condition Indicator) ──
+ // Same contract as PHQ9_SCORE / GAD7_SCORE: server-derived from a completed
+ // `MentalHealthAssessment`, `source = COMPUTED`, one row per administration.
+ // Note the inverted direction versus PHQ-9 / GAD-7: HIGHER is better for both.
+ WHO5_SCORE // score (0–100) — WHO-5 well-being percentage (raw 0–25 × 4). Opt-in. See MentalHealthAssessment.
+ SCI_SCORE // score (0–32) — Sleep Condition Indicator total; higher = better sleep. Opt-in. See MentalHealthAssessment.
@@map("measurement_type")
}
/// v1.25 — validated mental-health self-assessment instruments: PHQ-9
/// (depression) + GAD-7 (anxiety). The discriminator on
-/// `MentalHealthAssessment`; extensible (WHO-5 / PSS-10 are natural future
-/// siblings) without a schema change.
+/// `MentalHealthAssessment`. v1.27.9 adds the WHO-5 well-being index and the
+/// Sleep Condition Indicator (SCI) — same administration contract, inverted
+/// score direction (higher = better).
enum AssessmentInstrument {
PHQ9
GAD7
+ WHO5
+ SCI
@@map("assessment_instrument")
}
diff --git a/src/__tests__/i18n-english-leak-guard.test.ts b/src/__tests__/i18n-english-leak-guard.test.ts
index 9a3b00612..fadc16f8f 100644
--- a/src/__tests__/i18n-english-leak-guard.test.ts
+++ b/src/__tests__/i18n-english-leak-guard.test.ts
@@ -155,6 +155,39 @@ const LEGIT_IDENTICAL = new Set([
"mentalHealth.items.phq9.7",
"mentalHealth.items.phq9.8",
"mentalHealth.items.phq9.9",
+ // v1.27.9 — the Sleep Condition Indicator is validated in English only
+ // (Espie 2014, CC BY-NC). Item text, section stems and response anchors
+ // ship VERBATIM in the source language in every locale — a self-made
+ // translation would invalidate the instrument — and the UI shows a
+ // localized "validated in English" note instead. Identical-to-EN is the
+ // intended state here, not a leak.
+ "mentalHealth.items.sci.1",
+ "mentalHealth.items.sci.2",
+ "mentalHealth.items.sci.3",
+ "mentalHealth.items.sci.4",
+ "mentalHealth.items.sci.5",
+ "mentalHealth.items.sci.6",
+ "mentalHealth.items.sci.7",
+ "mentalHealth.items.sci.8",
+ "mentalHealth.stems.sci.night",
+ "mentalHealth.stems.sci.impact",
+ "mentalHealth.stems.sci.finally",
+ "mentalHealth.sciOptions.duration.0",
+ "mentalHealth.sciOptions.quality.4",
+ "mentalHealth.sciOptions.quality.3",
+ "mentalHealth.sciOptions.quality.2",
+ "mentalHealth.sciOptions.quality.1",
+ "mentalHealth.sciOptions.quality.0",
+ "mentalHealth.sciOptions.impact.4",
+ "mentalHealth.sciOptions.impact.3",
+ "mentalHealth.sciOptions.impact.2",
+ "mentalHealth.sciOptions.impact.1",
+ "mentalHealth.sciOptions.impact.0",
+ "mentalHealth.sciOptions.problemDuration.4",
+ "mentalHealth.sciOptions.problemDuration.3",
+ "mentalHealth.sciOptions.problemDuration.2",
+ "mentalHealth.sciOptions.problemDuration.1",
+ "mentalHealth.sciOptions.problemDuration.0",
]);
describe("i18n English-leak guard", () => {
diff --git a/src/app/api/mental-health/assessments/__tests__/route.test.ts b/src/app/api/mental-health/assessments/__tests__/route.test.ts
index ec64359db..24f2ba941 100644
--- a/src/app/api/mental-health/assessments/__tests__/route.test.ts
+++ b/src/app/api/mental-health/assessments/__tests__/route.test.ts
@@ -284,6 +284,98 @@ describe("POST /api/mental-health/assessments", () => {
expect(createArg.data.externalId).toBe("outbox-aa01");
});
+ it("scores a WHO-5 check-in as raw-sum × 4 and projects a WHO5_SCORE measurement", async () => {
+ // Raw 3+3+2+2+2 = 12 → reported 48 (below the WHO "below 50" cut → low).
+ vi.mocked(prisma.mentalHealthAssessment.create).mockResolvedValue({
+ id: "mha_who5",
+ instrument: "WHO5",
+ locale: "en",
+ version: "standard",
+ totalScore: 48,
+ severityBand: "low",
+ item9Flagged: false,
+ crisisShownAt: null,
+ takenAt: new Date("2026-06-28T00:00:00.000Z"),
+ createdAt: new Date("2026-06-28T00:00:00.000Z"),
+ } as never);
+
+ const res = await callPost(
+ makeReq({ instrument: "WHO5", items: [3, 3, 2, 2, 2] }),
+ );
+ expect(res.status).toBe(201);
+
+ const createArg = vi.mocked(prisma.mentalHealthAssessment.create).mock
+ .calls[0][0] as {
+ data: { totalScore: number; severityBand: string; item9Flagged: boolean };
+ };
+ expect(createArg.data.totalScore).toBe(48);
+ expect(createArg.data.severityBand).toBe("low");
+ // Positively-worded instrument, no safety item — never flagged.
+ expect(createArg.data.item9Flagged).toBe(false);
+
+ const measArg = vi.mocked(prisma.measurement.create).mock.calls[0][0] as {
+ data: { type: string; value: number; source: string };
+ };
+ expect(measArg.data.type).toBe("WHO5_SCORE");
+ expect(measArg.data.value).toBe(48);
+ expect(measArg.data.source).toBe("COMPUTED");
+
+ const body = (await res.json()) as {
+ data: { crisis: unknown; actionThreshold: number };
+ };
+ // No crisis routing on the WHO-5 — the ≤50 pointer is client-side copy.
+ expect(body.data.crisis).toBeNull();
+ expect(body.data.actionThreshold).toBe(50);
+ });
+
+ it("accepts an SCI check-in (0–4 anchors) and projects an SCI_SCORE measurement", async () => {
+ vi.mocked(prisma.mentalHealthAssessment.create).mockResolvedValue({
+ id: "mha_sci",
+ instrument: "SCI",
+ locale: "en",
+ version: "standard",
+ totalScore: 16,
+ severityBand: "belowThreshold",
+ item9Flagged: false,
+ crisisShownAt: null,
+ takenAt: new Date("2026-06-28T00:00:00.000Z"),
+ createdAt: new Date("2026-06-28T00:00:00.000Z"),
+ } as never);
+
+ // Total 4+3+2+1+0+4+1+1 = 16 → the paper's ≤16 probable-insomnia range.
+ const res = await callPost(
+ makeReq({ instrument: "SCI", items: [4, 3, 2, 1, 0, 4, 1, 1] }),
+ );
+ expect(res.status).toBe(201);
+
+ const createArg = vi.mocked(prisma.mentalHealthAssessment.create).mock
+ .calls[0][0] as { data: { totalScore: number; severityBand: string } };
+ expect(createArg.data.totalScore).toBe(16);
+ expect(createArg.data.severityBand).toBe("belowThreshold");
+
+ const measArg = vi.mocked(prisma.measurement.create).mock.calls[0][0] as {
+ data: { type: string; value: number; source: string };
+ };
+ expect(measArg.data.type).toBe("SCI_SCORE");
+ expect(measArg.data.value).toBe(16);
+ expect(measArg.data.source).toBe("COMPUTED");
+ });
+
+ it("rejects an out-of-scale answer for the instrument with 422 (SCI max 4, PHQ-9 max 3)", async () => {
+ // 5 is a legal WHO-5 answer but out of scale for the SCI…
+ const sciRes = await callPost(
+ makeReq({ instrument: "SCI", items: [5, 3, 2, 1, 0, 4, 1, 1] }),
+ );
+ expect(sciRes.status).toBe(422);
+ // …and 4 is out of scale for the PHQ-9.
+ const phqRes = await callPost(
+ makeReq({ instrument: "PHQ9", items: [4, 0, 0, 0, 0, 0, 0, 0, 0] }),
+ );
+ expect(phqRes.status).toBe(422);
+ expect(prisma.mentalHealthAssessment.create).not.toHaveBeenCalled();
+ expect(prisma.measurement.create).not.toHaveBeenCalled();
+ });
+
it("rejects a wrong item count with 422 and never writes", async () => {
const res = await callPost(
makeReq({ instrument: "PHQ9", items: [1, 2, 3] }), // 3 ≠ 9
diff --git a/src/app/api/mental-health/assessments/route.ts b/src/app/api/mental-health/assessments/route.ts
index 457d82dd1..939802cdb 100644
--- a/src/app/api/mental-health/assessments/route.ts
+++ b/src/app/api/mental-health/assessments/route.ts
@@ -229,8 +229,9 @@ async function postAssessment(request: NextRequest): Promise {
if (existing) return respondExisting(existing);
}
- // Server-authoritative scoring (the client never computes these).
- const total = scoreTotal(items);
+ // Server-authoritative scoring (the client never computes these). The
+ // total is the REPORTED score — the WHO-5 projects raw-sum × 4 (0–100).
+ const total = scoreTotal(id, items);
const band = severityBand(id, total);
const flagged = isSafetyFlagged(id, items);
const presentedLocale = locale ?? user.locale ?? "en";
diff --git a/src/components/measurement-reminders/vorsorge-section.tsx b/src/components/measurement-reminders/vorsorge-section.tsx
index 5c03acd8c..920ed7629 100644
--- a/src/components/measurement-reminders/vorsorge-section.tsx
+++ b/src/components/measurement-reminders/vorsorge-section.tsx
@@ -116,11 +116,11 @@ const TYPE_GROUPS = [
],
},
// v1.27.6 — the mental-wellbeing screenings become plannable like any
- // other Vorsorge item. Auto-resolve rides the server-written PHQ9_SCORE /
- // GAD7_SCORE row a completed check-in produces.
+ // other Vorsorge item. Auto-resolve rides the server-written *_SCORE row a
+ // completed check-in produces (v1.27.9 adds WHO-5 + SCI).
{
group: "mentalWellbeing",
- types: ["PHQ9_SCORE", "GAD7_SCORE"],
+ types: ["PHQ9_SCORE", "GAD7_SCORE", "WHO5_SCORE", "SCI_SCORE"],
},
] as const;
diff --git a/src/components/measurements/measurement-list-meta.ts b/src/components/measurements/measurement-list-meta.ts
index aabef8a6d..799c02bd5 100644
--- a/src/components/measurements/measurement-list-meta.ts
+++ b/src/components/measurements/measurement-list-meta.ts
@@ -125,6 +125,9 @@ export const MEASUREMENT_TYPE_LABEL_KEYS: Record = {
// ── v1.25 — clinical-signals wave ──
PHQ9_SCORE: "measurements.typePhq9Score",
GAD7_SCORE: "measurements.typeGad7Score",
+ // ── v1.27.9 — screening scores ──
+ WHO5_SCORE: "measurements.typeWho5Score",
+ SCI_SCORE: "measurements.typeSciScore",
GRIP_STRENGTH: "measurements.typeGripStrength",
PAIN_NRS: "measurements.typePainNrs",
WAIST_CIRCUMFERENCE: "measurements.typeWaistCircumference",
@@ -257,6 +260,9 @@ export const MEASUREMENT_TYPE_ICONS: Record = {
// Activity the pain trace; Scale the anthropometric waist pair.
PHQ9_SCORE: Gauge,
GAD7_SCORE: Gauge,
+ // ── v1.27.9 — screening scores ride the same composite-dial icon ──
+ WHO5_SCORE: Gauge,
+ SCI_SCORE: Gauge,
GRIP_STRENGTH: Dumbbell,
PAIN_NRS: Activity,
WAIST_CIRCUMFERENCE: Scale,
@@ -380,6 +386,9 @@ export const MEASUREMENT_TYPE_COLORS: Record = {
// chart-3 (cardio/alert family) for pain; chart-4 (body family) for waist.
PHQ9_SCORE: "bg-chart-1/20 text-chart-1",
GAD7_SCORE: "bg-chart-1/20 text-chart-1",
+ // ── v1.27.9 — screening scores join the composite/score colour family ──
+ WHO5_SCORE: "bg-chart-1/20 text-chart-1",
+ SCI_SCORE: "bg-chart-1/20 text-chart-1",
GRIP_STRENGTH: "bg-chart-1/20 text-chart-1",
PAIN_NRS: "bg-chart-3/20 text-chart-3",
WAIST_CIRCUMFERENCE: "bg-chart-4/20 text-chart-4",
diff --git a/src/components/mental-health/__tests__/mental-wellbeing.test.tsx b/src/components/mental-health/__tests__/mental-wellbeing.test.tsx
index 0c6868b49..5268847ee 100644
--- a/src/components/mental-health/__tests__/mental-wellbeing.test.tsx
+++ b/src/components/mental-health/__tests__/mental-wellbeing.test.tsx
@@ -11,9 +11,9 @@ import en from "../../../../messages/en.json";
* means clicks can't be driven here — the wizard's advance/back grammar is
* pinned in `check-in-nav.test.ts`. These tests pin the SSR-observable
* contracts: the disclaimer renders ONLY on the landing (never while testing),
- * a positive item-9 surfaces the crisis card, and the history paints its
- * dated list with severity + "support shown" badges — and NO trend chart
- * (v1.27.6: the score curve lives in Insights / Measurements, not here).
+ * a positive item-9 surfaces the crisis card, and (v1.27.9) the landing is
+ * intro + instrument cards only — the dated history + trend chart live in the
+ * per-instrument detail a card click opens, pinned to one instrument.
*/
vi.mock("@/lib/api/api-fetch", () => ({
@@ -26,6 +26,7 @@ import { CheckInWizard } from "../check-in-wizard";
import { AssessmentResult } from "../assessment-result";
import { AssessmentHistory } from "../assessment-history";
import { InstrumentCard } from "../instrument-card";
+import { InstrumentDetail } from "../instrument-detail";
import type { AssessmentRow, CreateResponse } from "../types";
const mh = en.mentalHealth;
@@ -178,18 +179,17 @@ describe("history rendering", () => {
},
];
- const html = withProviders();
+ const html = withProviders(
+ ,
+ );
- it("paints the dated list WITHOUT a trend chart shell", () => {
+ it("paints the pinned dated list with the trend-chart shell, no instrument toggle", () => {
+ // v1.27.9 — the history is pinned to one instrument inside the detail
+ // surface: no PHQ-9/GAD-7 tablist, and the lazy chart's skeleton shell
+ // holds the layout in SSR (recharts itself stays off first-load JS).
expect(html).toContain('data-slot="history-list"');
- // v1.27.6 — no chart on this surface: no lazy-chart skeleton, no
- // recharts container.
- expect(html).not.toContain('data-slot="skeleton"');
- expect(html).not.toContain("recharts");
- });
-
- it("shows the PHQ-9 / GAD-7 toggle and a severity badge", () => {
- expect(html).toContain('role="tablist"');
+ expect(html).toContain('data-pinned="PHQ9"');
+ expect(html).not.toContain('role="tablist"');
expect(html).toContain(mh.band.PHQ9.modSevere);
});
@@ -198,14 +198,175 @@ describe("history rendering", () => {
expect(html).toContain(mh.history.flaggedBadge);
});
- it("renders the empty state with no rows", () => {
- const empty = withProviders();
+ it("renders the empty state with no rows for the pinned instrument", () => {
+ const empty = withProviders(
+ ,
+ );
expect(empty).toContain(mh.history.empty);
expect(empty).not.toContain('data-slot="history-list"');
});
+
+ it("stays OFF the landing entirely (intro + cards only)", () => {
+ const landing = withProviders();
+ expect(landing).not.toContain('data-slot="mental-health-history"');
+ expect(landing).not.toContain('data-slot="history-list"');
+ });
});
-describe("instrument card (v1.27.6 — med-/Vorsorge card anatomy)", () => {
+describe("WHO-5 / SCI on the same infrastructure (v1.27.9)", () => {
+ it("renders all four instrument cards on the landing", () => {
+ const html = withProviders();
+ expect(html).toContain(mh.instrument.phq9);
+ expect(html).toContain(mh.instrument.gad7);
+ expect(html).toContain(mh.instrument.who5);
+ expect(html).toContain(mh.instrument.sci);
+ // The rhythm hint states both recall windows honestly.
+ expect(html).toContain(mh.pageRhythmHint);
+ });
+
+ it("WHO-5 wizard opens on item 1 with the six-point scale in source order and the recall stem", () => {
+ const html = withProviders(
+ {}}
+ onBack={() => {}}
+ isPending={false}
+ isError={false}
+ />,
+ );
+ expect(html).toContain(mh.items.who5["1"]);
+ expect(html).toContain('data-slot="check-in-stem"');
+ expect(html).toContain(mh.stems.who5.period);
+ // Six anchors, 5 → 0 (the WHO form leads with "All of the time").
+ for (const v of ["5", "4", "3", "2", "1", "0"] as const) {
+ expect(html).toContain(mh.who5Options[v]);
+ }
+ // English items are the validated wording for en — no translation note.
+ expect(html).not.toContain('data-slot="check-in-validated-note"');
+ // No functional follow-up outside the PHQ-9: 5 steps total.
+ expect(html).toContain("1 / 5");
+ });
+
+ it("SCI wizard opens on item 1 with the item-specific duration anchors and section stem", () => {
+ const html = withProviders(
+ {}}
+ onBack={() => {}}
+ isPending={false}
+ isError={false}
+ />,
+ );
+ expect(html).toContain(mh.items.sci["1"]);
+ expect(html).toContain(mh.stems.sci.night);
+ // Item 1 carries the sleep-latency time ranges, 4 → 0.
+ for (const v of ["4", "3", "2", "1", "0"] as const) {
+ expect(html).toContain(mh.sciOptions.duration[v]);
+ }
+ expect(html).toContain("1 / 8");
+ });
+
+ it("shows the gentle PHQ-9 pointer on a WHO-5 total of 50 or below", () => {
+ const result: CreateResponse = {
+ assessment: {
+ id: "w1",
+ instrument: "WHO5",
+ locale: "en",
+ totalScore: 48,
+ severityBand: "low",
+ item9Flagged: false,
+ crisisShownAt: null,
+ takenAt: "2026-06-20T00:00:00.000Z",
+ },
+ actionThreshold: 50,
+ crisis: null,
+ };
+ const html = withProviders(
+ {}}
+ onBack={() => {}}
+ />,
+ );
+ expect(html).toContain('data-slot="result-follow-up-hint"');
+ expect(html).toContain(mh.followUpHint.who5);
+ // Required WHO attribution rides the result view.
+ expect(html).toContain("CC BY-NC-SA 3.0 IGO");
+ expect(html).not.toContain('data-slot="mental-health-crisis-card"');
+ });
+
+ it("suppresses the hint on a good WHO-5 total (direction is inverted vs PHQ-9)", () => {
+ const result: CreateResponse = {
+ assessment: {
+ id: "w2",
+ instrument: "WHO5",
+ locale: "en",
+ totalScore: 80,
+ severityBand: "good",
+ item9Flagged: false,
+ crisisShownAt: null,
+ takenAt: "2026-06-20T00:00:00.000Z",
+ },
+ actionThreshold: 50,
+ crisis: null,
+ };
+ const html = withProviders(
+ {}}
+ onBack={() => {}}
+ />,
+ );
+ expect(html).not.toContain('data-slot="result-follow-up-hint"');
+ });
+
+ it("shows the neutral SCI band wording at 16 or below, with the Sleepio/BMJ attribution", () => {
+ const result: CreateResponse = {
+ assessment: {
+ id: "s1",
+ instrument: "SCI",
+ locale: "en",
+ totalScore: 14,
+ severityBand: "belowThreshold",
+ item9Flagged: false,
+ crisisShownAt: null,
+ takenAt: "2026-06-20T00:00:00.000Z",
+ },
+ actionThreshold: 16,
+ crisis: null,
+ };
+ const html = withProviders(
+ {}}
+ onBack={() => {}}
+ />,
+ );
+ expect(html).toContain(mh.followUpHint.sci);
+ expect(html).toContain(mh.band.SCI.belowThreshold);
+ expect(html).toContain("BMJ Open");
+ });
+
+ it("pinned history renders the WHO-5 / SCI series through the same component", () => {
+ const row: AssessmentRow = {
+ id: "t1",
+ instrument: "WHO5",
+ locale: "en",
+ totalScore: 72,
+ severityBand: "good",
+ item9Flagged: false,
+ crisisShownAt: null,
+ takenAt: "2026-06-20T00:00:00.000Z",
+ };
+ const html = withProviders(
+ ,
+ );
+ expect(html).toContain('data-pinned="WHO5"');
+ expect(html).toContain(mh.band.WHO5.good);
+ });
+});
+
+describe("instrument card (med-/Vorsorge card anatomy + detail opener)", () => {
const lastRow: AssessmentRow = {
id: "l1",
instrument: "PHQ9",
@@ -217,25 +378,43 @@ describe("instrument card (v1.27.6 — med-/Vorsorge card anatomy)", () => {
takenAt: "2026-06-20T00:00:00.000Z",
};
- it("renders the shared med-card header (title + category badge), no trend target", () => {
- const html = withProviders(
- {}} />,
+ function card(
+ instrument: AssessmentRow["instrument"],
+ last?: AssessmentRow,
+ ): string {
+ return withProviders(
+ {}}
+ onOpenDetail={() => {}}
+ />,
);
+ }
+
+ it("renders the shared med-card header and the clickable detail body", () => {
+ const html = card("PHQ9");
// The med-card header primitive paints the bold name + category badge.
expect(html).toContain(mh.instrument.phq9);
expect(html).toContain(mh.instrumentSub.phq9);
- // The former trend-detail button is gone…
- expect(html).not.toContain('data-slot="instrument-card-open"');
+ // v1.27.9 — the card body opens the per-instrument detail…
+ expect(html).toContain('data-slot="instrument-card-open"');
+ expect(html).toContain(mh.openDetail);
// …and the Start action remains the single bottom-pinned action.
expect(html).toContain(mh.start);
// No history yet → the calm no-check-in line, no fabricated dashes.
expect(html).toContain(mh.noResultYet);
+ // v1.27.9 — the required attribution footer rides every card.
+ expect(html).toContain('data-slot="instrument-card-attribution"');
+ });
+
+ it("carries the instrument's licence line on the WHO-5 / SCI cards", () => {
+ expect(card("WHO5")).toContain("CC BY-NC-SA 3.0 IGO");
+ expect(card("SCI")).toContain("Sleepio Limited");
});
it("shows last test (relative) and last result (score + band word)", () => {
- const html = withProviders(
- {}} />,
- );
+ const html = card("PHQ9", lastRow);
expect(html).toContain(mh.lastResult);
expect(html).toContain(mh.lastScore);
// Score + band word ride one value slot ("8 · Mild").
@@ -244,3 +423,46 @@ describe("instrument card (v1.27.6 — med-/Vorsorge card anatomy)", () => {
expect(html).not.toContain(mh.noResultYet);
});
});
+
+describe("instrument detail (v1.27.9 — opened from a card)", () => {
+ const rows: AssessmentRow[] = [
+ {
+ id: "d1",
+ instrument: "SCI",
+ locale: "en",
+ totalScore: 22,
+ severityBand: "aboveThreshold",
+ item9Flagged: false,
+ crisisShownAt: null,
+ takenAt: "2026-06-20T00:00:00.000Z",
+ },
+ {
+ id: "d2",
+ instrument: "PHQ9",
+ locale: "en",
+ totalScore: 4,
+ severityBand: "minimal",
+ item9Flagged: false,
+ crisisShownAt: null,
+ takenAt: "2026-06-01T00:00:00.000Z",
+ },
+ ];
+
+ it("shows last score + band, the Start action, the pinned history and the attribution", () => {
+ const html = withProviders(
+ {}} />,
+ );
+ expect(html).toContain('data-slot="instrument-detail"');
+ expect(html).toContain('data-slot="instrument-detail-last-score"');
+ expect(html).toContain(mh.band.SCI.aboveThreshold);
+ expect(html).toContain('data-slot="instrument-detail-start"');
+ expect(html).toContain(mh.start);
+ // The pinned history (chart shell + dated list) rides inside the detail.
+ expect(html).toContain('data-pinned="SCI"');
+ expect(html).toContain('data-slot="history-list"');
+ // …filtered to the pinned instrument: the PHQ-9 row stays out.
+ expect(html).not.toContain(mh.band.PHQ9.minimal);
+ // Required attribution line.
+ expect(html).toContain("Sleepio Limited");
+ });
+});
diff --git a/src/components/mental-health/assessment-history-chart.tsx b/src/components/mental-health/assessment-history-chart.tsx
new file mode 100644
index 000000000..95c4dc8fe
--- /dev/null
+++ b/src/components/mental-health/assessment-history-chart.tsx
@@ -0,0 +1,167 @@
+"use client";
+
+/**
+ * v1.25.3 — per-instrument screener trend chart.
+ *
+ * v1.25.5 — aligned to the dashboard's chart language exactly: a single
+ * `type="monotone"` line over the primary colour with a soft gradient area
+ * fill beneath (same `fillOpacity` + margins the dashboard line charts use),
+ * the shared grid / axis tokens, and `RichChartTooltip`. The earlier
+ * severity-band `ReferenceArea`s — unique to this surface — are gone so it
+ * reads like every other trend; the band still surfaces in the tooltip.
+ *
+ * v1.27.9 — back on the surface, but only inside the per-instrument detail
+ * (opened from a card): the landing stays free of any score curve, so the
+ * trend is opt-in behind a deliberate click. The Y domain and band wording
+ * come from the instrument registry, so the WHO-5 (0–100, higher = better)
+ * and the SCI (0–32, higher = better) render through the same component.
+ *
+ * One instrument at a time: the ranges and band sets differ, so the parent
+ * pins which series this paints.
+ */
+import { useMemo } from "react";
+import {
+ Area,
+ CartesianGrid,
+ ComposedChart,
+ Line,
+ ResponsiveContainer,
+ Tooltip,
+ XAxis,
+ YAxis,
+} from "recharts";
+
+import { RichChartTooltip, type RichTooltipRow } from "../charts/chart-tooltip";
+import { CHART_HEIGHT_PX } from "@/lib/charts/constants";
+import { prefersReducedMotion } from "@/lib/charts/reduced-motion";
+import { formatDateShort } from "@/lib/format";
+import { useTranslations } from "@/lib/i18n/context";
+import { INSTRUMENTS } from "@/lib/mental-health/instruments";
+
+import type { AssessmentRow, InstrumentId } from "./types";
+
+interface ChartPoint {
+ timestamp: number;
+ value: number;
+ band: string;
+}
+
+export function AssessmentHistoryChart({
+ instrument,
+ rows,
+}: {
+ instrument: InstrumentId;
+ /** Rows for THIS instrument, any order (sorted internally, ascending time). */
+ rows: AssessmentRow[];
+}) {
+ const { t } = useTranslations();
+ const def = INSTRUMENTS[instrument];
+
+ const points = useMemo(() => {
+ return [...rows]
+ .sort(
+ (a, b) => new Date(a.takenAt).getTime() - new Date(b.takenAt).getTime(),
+ )
+ .map((r) => ({
+ timestamp: new Date(r.takenAt).getTime(),
+ value: r.totalScore,
+ band: r.severityBand,
+ }));
+ }, [rows]);
+
+ const animate = !prefersReducedMotion();
+ const primary = "var(--primary)";
+ const gradientId = `mh-trend-${instrument}`;
+
+ if (points.length === 0) {
+ return (
+
+ {t("mentalHealth.history.empty")}
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ formatDateShort(new Date(ts))}
+ tick={{ fontSize: 11, fill: "var(--muted-foreground)" }}
+ stroke="var(--border)"
+ minTickGap={32}
+ />
+
+ {
+ const active = props.active ?? false;
+ const payload = props.payload as
+ ReadonlyArray<{ payload?: ChartPoint }> | undefined;
+ const point = payload?.[0]?.payload;
+ if (!active || !point) {
+ return ;
+ }
+ const rowsOut: RichTooltipRow[] = [
+ {
+ name: t("mentalHealth.history.totalLabel"),
+ value: String(point.value),
+ color: primary,
+ },
+ {
+ name: t("mentalHealth.history.bandLabel"),
+ value: t(`mentalHealth.band.${instrument}.${point.band}`),
+ color: "var(--muted-foreground)",
+ },
+ ];
+ return (
+
+ );
+ }}
+ />
+
+
+
+
+ );
+}
diff --git a/src/components/mental-health/assessment-history.tsx b/src/components/mental-health/assessment-history.tsx
index 1bda8bfe8..570e4c568 100644
--- a/src/components/mental-health/assessment-history.tsx
+++ b/src/components/mental-health/assessment-history.tsx
@@ -1,30 +1,31 @@
"use client";
/**
- * v1.25.3 — the history card: a dated list with severity badges.
+ * v1.25.3 — the dated result history with severity badges.
*
- * v1.27.6 — the trend CHART is gone from this surface. A rising score curve
- * on the check-in page itself reads as a verdict at the exact moment someone
- * is taking stock of how they feel; the page stays a calm list of past
- * check-ins. The score trend still exists — the totals ride PHQ9_SCORE /
- * GAD7_SCORE measurement rows, so Insights / Measurements carry the curve
- * for whoever seeks it out.
+ * v1.27.9 — pinned-only: the combined landing card (with the instrument
+ * toggle) is gone; the landing shows intro + instrument cards and nothing
+ * else. This component now renders ONE instrument's detail body — trend
+ * chart (lazy Recharts, the labs-detail precedent) over the dated list —
+ * inside the detail surface a card click opens. The history is deliberately
+ * opt-in behind that click, never pushed onto the main page.
*
- * PHQ-9 / GAD-7 toggle: the two instruments have different score ranges +
- * bands, so the list shows one at a time.
- *
- * CRISIS-SAFETY: a flagged PHQ-9 row carries a discreet "support shown" marker;
- * tapping it re-surfaces the STATIC crisis-resource card derived from the row's
- * stored locale (`crisisResourcesForLocale`) — it NEVER decrypts or reveals the
- * item-9 answer, and surfacing it triggers no third-party alert. No new data
- * leaves the safety boundary.
+ * CRISIS-SAFETY: a flagged PHQ-9 row carries a discreet "support shown"
+ * marker; tapping it re-surfaces the STATIC crisis-resource card derived from
+ * the row's stored locale (`crisisResourcesForLocale`) — it NEVER decrypts or
+ * reveals the item-9 answer, and surfacing it triggers no third-party alert.
+ * No new data leaves the safety boundary.
*/
import { useMemo, useState } from "react";
+import dynamic from "next/dynamic";
+import type { ComponentProps } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { EmptyState } from "@/components/ui/empty-state";
+import { ChartErrorBoundary } from "@/components/charts/chart-error-state";
+import { ChartSkeleton } from "@/components/charts/chart-skeleton";
+import { importWithRetry } from "@/lib/retry-import";
import { useFormatters, useTranslations } from "@/lib/i18n/context";
import { crisisResourcesForLocale } from "@/lib/mental-health/crisis-resources";
import { Activity } from "lucide-react";
@@ -32,8 +33,24 @@ import { Activity } from "lucide-react";
import { CrisisCard } from "./crisis-card";
import type { AssessmentRow, CrisisSet, InstrumentId } from "./types";
-function lower(id: InstrumentId): "phq9" | "gad7" {
- return id === "PHQ9" ? "phq9" : "gad7";
+// Defer the recharts trend chart so recharts stays off the surface's
+// first-load JS (the labs detail precedent). The `` shell
+// holds the layout.
+const AssessmentHistoryChartLazy = dynamic(
+ () =>
+ importWithRetry(() => import("./assessment-history-chart")).then((mod) => ({
+ default: mod.AssessmentHistoryChart,
+ })),
+ { ssr: false, loading: () => },
+);
+function AssessmentHistoryChart(
+ props: ComponentProps,
+) {
+ return (
+
+
+
+ );
}
/** Re-derive the STATIC crisis set from a row's locale — no decryption. */
@@ -48,122 +65,92 @@ function crisisFromRow(row: AssessmentRow): CrisisSet {
};
}
-export function AssessmentHistory({ rows }: { rows: AssessmentRow[] }) {
+export function AssessmentHistory({
+ rows,
+ instrument,
+}: {
+ /** ALL assessments (any instrument); filtered internally. */
+ rows: AssessmentRow[];
+ /** The instrument this detail surface is pinned to. */
+ instrument: InstrumentId;
+}) {
const { t } = useTranslations();
const { date: formatDate } = useFormatters();
- const [tab, setTab] = useState("PHQ9");
// Which flagged row's crisis card is currently expanded (re-surfaced).
const [openCrisisRowId, setOpenCrisisRowId] = useState(null);
- const forTab = useMemo(
+ const forInstrument = useMemo(
() =>
rows
- .filter((r) => r.instrument === tab)
+ .filter((r) => r.instrument === instrument)
.sort(
(a, b) =>
new Date(b.takenAt).getTime() - new Date(a.takenAt).getTime(),
),
- [rows, tab],
+ [rows, instrument],
);
- const isEmpty = rows.length === 0;
+ if (forInstrument.length === 0) {
+ return (
+
);
}
diff --git a/src/components/mental-health/assessment-result.tsx b/src/components/mental-health/assessment-result.tsx
index 67cb1b6cb..ba3611ee7 100644
--- a/src/components/mental-health/assessment-result.tsx
+++ b/src/components/mental-health/assessment-result.tsx
@@ -6,6 +6,12 @@
* another". Content is unchanged from the v1.25 monolith; it is re-housed as
* one stack inside the page spine instead of loose top-level sections.
*
+ * v1.27.9 — direction-aware follow-up hints from the registry: PHQ-9/GAD-7
+ * keep the ≥10 professional-help suggestion, a WHO-5 total of 50 or below
+ * gets a gentle pointer to the PHQ-9 check-in (per the WHO scoring comment),
+ * and an SCI total of 16 or below gets the paper's neutral band wording.
+ * The attribution footer now comes from the instrument definition.
+ *
* SAFETY: the crisis set rides the POST response (`crisis`) on any non-zero
* item 9 and renders immediately; this surface never invites an AI Coach
* conversation about item content.
@@ -16,14 +22,10 @@ import { ArrowLeft } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useTranslations } from "@/lib/i18n/context";
-import { PHQ_GAD_ATTRIBUTION } from "@/lib/mental-health/instruments";
+import { INSTRUMENTS, needsFollowUp } from "@/lib/mental-health/instruments";
import { CrisisCard } from "./crisis-card";
-import type { CreateResponse, InstrumentId } from "./types";
-
-function lower(id: InstrumentId): "phq9" | "gad7" {
- return id === "PHQ9" ? "phq9" : "gad7";
-}
+import type { CreateResponse } from "./types";
export function AssessmentResult({
result,
@@ -36,7 +38,8 @@ export function AssessmentResult({
}) {
const { t } = useTranslations();
const instrument = result.assessment.instrument;
- const key = lower(instrument);
+ const def = INSTRUMENTS[instrument];
+ const key = def.i18nKey;
// a11y: on submit the wizard unmounts and this view mounts; move focus to the
// result heading so keyboard / screen-reader users land on the new content
@@ -104,9 +107,16 @@ export function AssessmentResult({
)}
- {result.assessment.totalScore >= result.actionThreshold && (
-
- {t("mentalHealth.considerProfessional")}
+ {/* Direction-aware follow-up hint: PHQ-9/GAD-7 point up (≥ 10),
+ WHO-5 (≤ 50 → gentle PHQ-9 pointer) and SCI (≤ 16 → the paper's
+ neutral band wording) point down. Soft suggestions, never a
+ diagnosis. */}
+ {needsFollowUp(instrument, result.assessment.totalScore) && (
+
- {SCALE.map((v) => {
+ {scale.map((v) => {
const selected = onFunctionalStep
? functional === v
: items[questionIndex] === v;
@@ -180,7 +208,9 @@ export function CheckInWizard({
>
{onFunctionalStep
? t(`mentalHealth.functional.${v}`)
- : t(`mentalHealth.options.${v}`)}
+ : t(
+ `mentalHealth.${optionLabelKey(instrument, questionIndex, v)}`,
+ )}
);
})}
diff --git a/src/components/mental-health/instrument-card.tsx b/src/components/mental-health/instrument-card.tsx
index 3f0858500..e908ae919 100644
--- a/src/components/mental-health/instrument-card.tsx
+++ b/src/components/mental-health/instrument-card.tsx
@@ -3,54 +3,81 @@
/**
* v1.25.3 — one neutral card per instrument on the landing.
*
- * v1.27.6 — rebuilt onto the shared med-/Vorsorge card anatomy so the two
- * test cards read exactly like their peers: `MedicationCardHeader` (bold
+ * v1.27.6 — rebuilt onto the shared med-/Vorsorge card anatomy so the test
+ * cards read exactly like their peers: `MedicationCardHeader` (bold
* name + outline category badge), a label/value block in the next-last
* grammar ("last test" → relative day, "last result" → score + band word),
* and a single bottom-pinned Start action. The surface stays NEUTRAL
* regardless of the last band — no severity tint (house rule: status reads
* through discreet text only, never a card wash).
+ *
+ * v1.27.9 — the card generalises over the four-instrument registry and
+ * carries the instrument's required attribution/licence line as a small
+ * factual footer (WHO CC BY-NC-SA citation for the WHO-5, the Sleepio
+ * CC BY-NC citation for the SCI, the Pfizer grant for PHQ/GAD). A locale
+ * without validated item wording additionally gets the honest
+ * "validated in English" note. The card BODY (header + last-result block)
+ * opens the per-instrument detail — the Vorsorge-/med-card interaction —
+ * while the bottom-pinned Start button stays a separate, non-overlapping
+ * action.
*/
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { MedicationCardHeader } from "@/components/medications/medication-card-header";
import { useFormatters, useTranslations } from "@/lib/i18n/context";
import { relativeCalendarDate } from "@/lib/i18n/relative-time";
+import {
+ INSTRUMENTS,
+ hasValidatedItems,
+} from "@/lib/mental-health/instruments";
import type { AssessmentRow, InstrumentId } from "./types";
-function lower(id: InstrumentId): "phq9" | "gad7" {
- return id === "PHQ9" ? "phq9" : "gad7";
-}
-
export function InstrumentCard({
instrument,
last,
onStart,
+ onOpenDetail,
}: {
instrument: InstrumentId;
/** Most-recent assessment for this instrument, or undefined when none yet. */
last: AssessmentRow | undefined;
onStart: () => void;
+ /**
+ * Open this instrument's detail (last score + band, trend chart, dated
+ * history). The card body is the target; Start stays separate.
+ */
+ onOpenDetail: () => void;
}) {
- const { t } = useTranslations();
+ const { t, locale } = useTranslations();
const { date: formatDate } = useFormatters();
- const key = lower(instrument);
+ const def = INSTRUMENTS[instrument];
+ const key = def.i18nKey;
const title = t(`mentalHealth.instrument.${key}`);
return (
-
-
+ {/* Body is the detail target: clicking the header / last-result block
+ opens this instrument's detail (chart + dated history), like a
+ medication card. The Start button below stays a separate action. */}
+