mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
i18n(doctor-report): translate PDF medical report strings into es/fr/it/pl
Localises the doctor-report PDF surface: cover-page metadata, vitals
table headings (parameter / current / period mean / n), BP classification
(ESH 2023 bands: optimal / normal / high-normal / hypertension grades),
BMI classification (WHO underweight → obesity grade III), mood scale,
glucose reference ranges (ADA 2024 / DDG, fasting / postprandial /
random / bedtime), GLP-1 weight summary, compliance table, section-
picker dialog (date range, practice name, presets, validation errors).
ICU placeholders {start}, {end}, {delta}, {label}, {avg}, {unit},
{rangeMin}, {rangeMax}, {class} preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b776daf546
commit
11256935f7
7 files changed
+388
-273
No files matched your search
+64
-64
@@ -51,23 +51,23 @@
|
||||
"period": "Período del informe",
|
||||
"createdOn": "Creado el",
|
||||
"vitalsTitle": "Signos vitales — Resumen",
|
||||
"colParameter": "Parameter",
|
||||
"colCurrent": "Current",
|
||||
"colAvgPeriod": "Avg. period",
|
||||
"colParameter": "Parámetro",
|
||||
"colCurrent": "Actual",
|
||||
"colAvgPeriod": "Med. periodo",
|
||||
"colMin": "Mín",
|
||||
"colMax": "Máx",
|
||||
"colN": "n",
|
||||
"bpClassificationTitle": "Blood pressure — ESH classification (2023)",
|
||||
"avgBp": "Average blood pressure",
|
||||
"classification": "Classification",
|
||||
"bmiTitle": "Body Mass Index (BMI)",
|
||||
"bpClassificationTitle": "Tensión arterial — Clasificación ESH (2023)",
|
||||
"avgBp": "Tensión arterial media",
|
||||
"classification": "Clasificación",
|
||||
"bmiTitle": "Índice de masa corporal (IMC)",
|
||||
"bmiRow": "IMC:{bmi} kg/m² — Classification: {class} (WHO)",
|
||||
"complianceTitle": "Medication compliance",
|
||||
"complianceTitle": "Cumplimiento de medicación",
|
||||
"glp1Title": "Terapia GLP-1",
|
||||
"glp1WeightSummary": "Weight over report period: {start} kg → {end} kg ({delta} kg)",
|
||||
"glp1WeightSummary": "Peso en el periodo del informe: {start} kg → {end} kg ({delta} kg)",
|
||||
"glp1CurrentDose": "Dosis actual:{value} {unit} (since {since})",
|
||||
"glp1Compliance": "Cumplimiento:{taken} / {total} ({rate}%)",
|
||||
"glp1SideEffectsTitle": "Side effects logged",
|
||||
"glp1SideEffectsTitle": "Efectos secundarios registrados",
|
||||
"colGlp1Date": "Fecha",
|
||||
"colGlp1Dose": "Dosis",
|
||||
"colGlp1Note": "Nota",
|
||||
@@ -78,79 +78,79 @@
|
||||
"colMissed": "Perdida",
|
||||
"colTotal": "Total",
|
||||
"colComplianceRate": "Tasa de cumplimiento",
|
||||
"moodTitle": "Mood trajectory",
|
||||
"moodTitle": "Evolución del estado de ánimo",
|
||||
"moodSummary": "Promedio:{avg} / 5.0 · Entries: {count} · Range: {min}–{max}",
|
||||
"colMood": "Estado de ánimo",
|
||||
"colCount": "Count",
|
||||
"colShare": "Share",
|
||||
"colCount": "Cantidad",
|
||||
"colShare": "Porcentaje",
|
||||
"footerDisclaimer1": "Este informe se generó automáticamente a partir de datos autorregistrados y está destinado a apoyar",
|
||||
"footerDisclaimer2": "la conversación entre médico y paciente. No sustituye un diagnóstico médico. Las correlaciones no implican causalidad.",
|
||||
"footerSource": "Fuente: HealthLog | Zona horaria: Europe/Berlin | Creado: {timestamp}",
|
||||
"typeWeight": "Body weight",
|
||||
"typeBpSys": "Systolic blood pressure",
|
||||
"typeBpDia": "Diastolic blood pressure",
|
||||
"typePulse": "Resting pulse",
|
||||
"typeWeight": "Peso corporal",
|
||||
"typeBpSys": "Tensión arterial sistólica",
|
||||
"typeBpDia": "Tensión arterial diastólica",
|
||||
"typePulse": "Pulso en reposo",
|
||||
"typeBodyFat": "Grasa corporal",
|
||||
"typeSleep": "Sleep duration",
|
||||
"typeSteps": "Activity (steps)",
|
||||
"typeTotalBodyWater": "Total body water",
|
||||
"typeBoneMass": "Bone mass",
|
||||
"typeOxygenSaturation": "Oxygen saturation",
|
||||
"typeSleep": "Duración del sueño",
|
||||
"typeSteps": "Actividad (pasos)",
|
||||
"typeTotalBodyWater": "Agua corporal total",
|
||||
"typeBoneMass": "Masa ósea",
|
||||
"typeOxygenSaturation": "Saturación de oxígeno",
|
||||
"unitSteps": "Pasos",
|
||||
"bmiUnderweight": "Underweight",
|
||||
"bmiNormal": "Normal weight",
|
||||
"bmiOverweight": "Overweight (pre-obese)",
|
||||
"bmiObeseGrade1": "Obesity grade I",
|
||||
"bmiObeseGrade2": "Obesity grade II",
|
||||
"bmiObeseGrade3": "Obesity grade III",
|
||||
"bpOptimal": "Optimal",
|
||||
"bmiUnderweight": "Peso insuficiente",
|
||||
"bmiNormal": "Peso normal",
|
||||
"bmiOverweight": "Sobrepeso (preobesidad)",
|
||||
"bmiObeseGrade1": "Obesidad grado I",
|
||||
"bmiObeseGrade2": "Obesidad grado II",
|
||||
"bmiObeseGrade3": "Obesidad grado III",
|
||||
"bpOptimal": "Óptima",
|
||||
"bpNormal": "Normal",
|
||||
"bpHighNormal": "High-normal",
|
||||
"bpHypertensionGrade1": "Hypertension grade 1",
|
||||
"bpHypertensionGrade2": "Hypertension grade 2",
|
||||
"bpHypertensionGrade3": "Hypertension grade 3",
|
||||
"moodAwful": "Very bad",
|
||||
"bpHighNormal": "Normal-alta",
|
||||
"bpHypertensionGrade1": "Hipertensión grado 1",
|
||||
"bpHypertensionGrade2": "Hipertensión grado 2",
|
||||
"bpHypertensionGrade3": "Hipertensión grado 3",
|
||||
"moodAwful": "Muy mal",
|
||||
"moodBad": "Mal",
|
||||
"moodNeutral": "Neutral",
|
||||
"moodGood": "Bien",
|
||||
"moodGreat": "Very good",
|
||||
"typeGlucoseFasting": "Glucose (fasting)",
|
||||
"typeGlucosePostprandial": "Glucose (postprandial)",
|
||||
"typeGlucoseRandom": "Glucose (random)",
|
||||
"typeGlucoseBedtime": "Glucose (bedtime)",
|
||||
"glucoseClassificationTitle": "Blood glucose — reference ranges (ADA 2024 / DDG)",
|
||||
"glucoseRow": "{label}: avg {avg} {unit} (target {rangeMin}–{rangeMax}) — {class}",
|
||||
"glucoseInTarget": "in target",
|
||||
"glucoseBelowTarget": "below target",
|
||||
"glucoseAboveTarget": "above target",
|
||||
"practiceLabel": "Practice",
|
||||
"moodGreat": "Muy bien",
|
||||
"typeGlucoseFasting": "Glucosa (en ayunas)",
|
||||
"typeGlucosePostprandial": "Glucosa (posprandial)",
|
||||
"typeGlucoseRandom": "Glucosa (aleatoria)",
|
||||
"typeGlucoseBedtime": "Glucosa (antes de dormir)",
|
||||
"glucoseClassificationTitle": "Glucemia — Rangos de referencia (ADA 2024 / DDG)",
|
||||
"glucoseRow": "{label}: med. {avg} {unit} (objetivo {rangeMin}–{rangeMax}) — {class}",
|
||||
"glucoseInTarget": "en objetivo",
|
||||
"glucoseBelowTarget": "por debajo del objetivo",
|
||||
"glucoseAboveTarget": "por encima del objetivo",
|
||||
"practiceLabel": "Consulta",
|
||||
"dialog": {
|
||||
"title": "Doctor report (PDF)",
|
||||
"description": "Pick the reporting period and (optionally) the practice name; both are printed on the cover.",
|
||||
"startLabel": "Start date",
|
||||
"endLabel": "End date",
|
||||
"practiceLabel": "Practice / clinic name (optional)",
|
||||
"practicePlaceholder": "e.g. Dr. Müller — Family practice",
|
||||
"title": "Informe médico (PDF)",
|
||||
"description": "Define el periodo de análisis y (opcional) el nombre de la consulta — ambos aparecen en la portada.",
|
||||
"startLabel": "Desde",
|
||||
"endLabel": "Hasta",
|
||||
"practiceLabel": "Consulta / clínica (opcional)",
|
||||
"practicePlaceholder": "p. ej. Dr. Müller — Medicina familiar",
|
||||
"preset90d": "Últimos 90 días",
|
||||
"preset180d": "Last 6 months",
|
||||
"preset365d": "Last 12 months",
|
||||
"submit": "Generate PDF",
|
||||
"preset180d": "Últimos 6 meses",
|
||||
"preset365d": "Últimos 12 meses",
|
||||
"submit": "Generar PDF",
|
||||
"cancel": "Cancelar",
|
||||
"errorEndBeforeStart": "End date must be on or after the start date.",
|
||||
"errorRangeTooLong": "The reporting period cannot exceed 2 years.",
|
||||
"errorInvalidDate": "Please select a valid start and end date."
|
||||
"errorEndBeforeStart": "La fecha final no puede ser anterior a la inicial.",
|
||||
"errorRangeTooLong": "El periodo de análisis no puede superar los 2 años.",
|
||||
"errorInvalidDate": "Selecciona una fecha de inicio y fin válida."
|
||||
},
|
||||
"sections": {
|
||||
"title": "Choose sections",
|
||||
"title": "Seleccionar contenido",
|
||||
"bp": "Presión arterial",
|
||||
"weight": "Peso",
|
||||
"pulse": "Resting pulse",
|
||||
"bmi": "Body Mass Index (BMI)",
|
||||
"pulse": "Pulso en reposo",
|
||||
"bmi": "Índice de masa corporal (IMC)",
|
||||
"mood": "Estado de ánimo",
|
||||
"moodSensitive": "Sensitive — off by default.",
|
||||
"compliance": "Medication compliance",
|
||||
"sleep": "Sleep duration",
|
||||
"empty": "No data was recorded in this date range yet. The report will still include the cover page."
|
||||
"moodSensitive": "Sensible — desactivado por defecto.",
|
||||
"compliance": "Cumplimiento de medicación",
|
||||
"sleep": "Duración del sueño",
|
||||
"empty": "No hay datos para el periodo seleccionado. El informe se generará con la portada."
|
||||
}
|
||||
},
|
||||
"errorBoundary": {
|
||||
|
||||
+61
-61
@@ -51,23 +51,23 @@
|
||||
"period": "Période du rapport",
|
||||
"createdOn": "Créé le",
|
||||
"vitalsTitle": "Signes vitaux — Résumé",
|
||||
"colParameter": "Parameter",
|
||||
"colCurrent": "Current",
|
||||
"colAvgPeriod": "Avg. period",
|
||||
"colParameter": "Paramètre",
|
||||
"colCurrent": "Actuel",
|
||||
"colAvgPeriod": "Moy. période",
|
||||
"colMin": "Min",
|
||||
"colMax": "Max",
|
||||
"colN": "n",
|
||||
"bpClassificationTitle": "Blood pressure — ESH classification (2023)",
|
||||
"avgBp": "Average blood pressure",
|
||||
"bpClassificationTitle": "Tension artérielle — Classification ESH (2023)",
|
||||
"avgBp": "Tension artérielle moyenne",
|
||||
"classification": "Classification",
|
||||
"bmiTitle": "Body Mass Index (BMI)",
|
||||
"bmiTitle": "Indice de masse corporelle (IMC)",
|
||||
"bmiRow": "IMC:{bmi} kg/m² — Classification: {class} (WHO)",
|
||||
"complianceTitle": "Medication compliance",
|
||||
"complianceTitle": "Observance des médicaments",
|
||||
"glp1Title": "Traitement GLP-1",
|
||||
"glp1WeightSummary": "Weight over report period: {start} kg → {end} kg ({delta} kg)",
|
||||
"glp1WeightSummary": "Poids sur la période du rapport : {start} kg → {end} kg ({delta} kg)",
|
||||
"glp1CurrentDose": "Dose actuelle:{value} {unit} (since {since})",
|
||||
"glp1Compliance": "Observance:{taken} / {total} ({rate}%)",
|
||||
"glp1SideEffectsTitle": "Side effects logged",
|
||||
"glp1SideEffectsTitle": "Effets secondaires consignés",
|
||||
"colGlp1Date": "Date",
|
||||
"colGlp1Dose": "Dose",
|
||||
"colGlp1Note": "Note",
|
||||
@@ -78,79 +78,79 @@
|
||||
"colMissed": "Manqué",
|
||||
"colTotal": "Total",
|
||||
"colComplianceRate": "Taux d'observance",
|
||||
"moodTitle": "Mood trajectory",
|
||||
"moodTitle": "Évolution de l’humeur",
|
||||
"moodSummary": "Moyenne:{avg} / 5.0 · Entries: {count} · Range: {min}–{max}",
|
||||
"colMood": "Humeur",
|
||||
"colCount": "Count",
|
||||
"colShare": "Share",
|
||||
"colCount": "Nombre",
|
||||
"colShare": "Part",
|
||||
"footerDisclaimer1": "Ce rapport a été généré automatiquement à partir de données auto-déclarées et vise à soutenir",
|
||||
"footerDisclaimer2": "la conversation médecin-patient. Il ne remplace pas un diagnostic médical. Les corrélations n'impliquent pas la causalité.",
|
||||
"footerSource": "Source : HealthLog | Fuseau horaire : Europe/Berlin | Créé : {timestamp}",
|
||||
"typeWeight": "Body weight",
|
||||
"typeBpSys": "Systolic blood pressure",
|
||||
"typeBpDia": "Diastolic blood pressure",
|
||||
"typePulse": "Resting pulse",
|
||||
"typeWeight": "Poids corporel",
|
||||
"typeBpSys": "Tension artérielle systolique",
|
||||
"typeBpDia": "Tension artérielle diastolique",
|
||||
"typePulse": "Pouls au repos",
|
||||
"typeBodyFat": "Masse grasse",
|
||||
"typeSleep": "Sleep duration",
|
||||
"typeSteps": "Activity (steps)",
|
||||
"typeTotalBodyWater": "Total body water",
|
||||
"typeBoneMass": "Bone mass",
|
||||
"typeOxygenSaturation": "Oxygen saturation",
|
||||
"typeSleep": "Durée du sommeil",
|
||||
"typeSteps": "Activité (pas)",
|
||||
"typeTotalBodyWater": "Eau corporelle totale",
|
||||
"typeBoneMass": "Masse osseuse",
|
||||
"typeOxygenSaturation": "Saturation en oxygène",
|
||||
"unitSteps": "Pas",
|
||||
"bmiUnderweight": "Underweight",
|
||||
"bmiNormal": "Normal weight",
|
||||
"bmiOverweight": "Overweight (pre-obese)",
|
||||
"bmiObeseGrade1": "Obesity grade I",
|
||||
"bmiObeseGrade2": "Obesity grade II",
|
||||
"bmiObeseGrade3": "Obesity grade III",
|
||||
"bpOptimal": "Optimal",
|
||||
"bpNormal": "Normal",
|
||||
"bpHighNormal": "High-normal",
|
||||
"bmiUnderweight": "Insuffisance pondérale",
|
||||
"bmiNormal": "Poids normal",
|
||||
"bmiOverweight": "Surpoids (préobésité)",
|
||||
"bmiObeseGrade1": "Obésité grade I",
|
||||
"bmiObeseGrade2": "Obésité grade II",
|
||||
"bmiObeseGrade3": "Obésité grade III",
|
||||
"bpOptimal": "Optimale",
|
||||
"bpNormal": "Normale",
|
||||
"bpHighNormal": "Normale haute",
|
||||
"bpHypertensionGrade1": "Hypertension grade 1",
|
||||
"bpHypertensionGrade2": "Hypertension grade 2",
|
||||
"bpHypertensionGrade3": "Hypertension grade 3",
|
||||
"moodAwful": "Very bad",
|
||||
"moodAwful": "Très mauvaise",
|
||||
"moodBad": "Mauvais",
|
||||
"moodNeutral": "Neutre",
|
||||
"moodGood": "Bien",
|
||||
"moodGreat": "Very good",
|
||||
"typeGlucoseFasting": "Glucose (fasting)",
|
||||
"typeGlucosePostprandial": "Glucose (postprandial)",
|
||||
"typeGlucoseRandom": "Glucose (random)",
|
||||
"typeGlucoseBedtime": "Glucose (bedtime)",
|
||||
"glucoseClassificationTitle": "Blood glucose — reference ranges (ADA 2024 / DDG)",
|
||||
"glucoseRow": "{label}: avg {avg} {unit} (target {rangeMin}–{rangeMax}) — {class}",
|
||||
"glucoseInTarget": "in target",
|
||||
"glucoseBelowTarget": "below target",
|
||||
"glucoseAboveTarget": "above target",
|
||||
"practiceLabel": "Practice",
|
||||
"moodGreat": "Très bonne",
|
||||
"typeGlucoseFasting": "Glycémie (à jeun)",
|
||||
"typeGlucosePostprandial": "Glycémie (postprandiale)",
|
||||
"typeGlucoseRandom": "Glycémie (indifférente)",
|
||||
"typeGlucoseBedtime": "Glycémie (avant le coucher)",
|
||||
"glucoseClassificationTitle": "Glycémie — Plages de référence (ADA 2024 / DDG)",
|
||||
"glucoseRow": "{label} : moy. {avg} {unit} (cible {rangeMin}–{rangeMax}) — {class}",
|
||||
"glucoseInTarget": "dans la cible",
|
||||
"glucoseBelowTarget": "sous la cible",
|
||||
"glucoseAboveTarget": "au-dessus de la cible",
|
||||
"practiceLabel": "Cabinet",
|
||||
"dialog": {
|
||||
"title": "Doctor report (PDF)",
|
||||
"description": "Pick the reporting period and (optionally) the practice name; both are printed on the cover.",
|
||||
"startLabel": "Start date",
|
||||
"endLabel": "End date",
|
||||
"practiceLabel": "Practice / clinic name (optional)",
|
||||
"practicePlaceholder": "e.g. Dr. Müller — Family practice",
|
||||
"title": "Rapport médical (PDF)",
|
||||
"description": "Définis la période d’analyse et (optionnel) le nom du cabinet — les deux apparaîtront sur la page de garde.",
|
||||
"startLabel": "Du",
|
||||
"endLabel": "Au",
|
||||
"practiceLabel": "Cabinet / clinique (optionnel)",
|
||||
"practicePlaceholder": "p. ex. Dr Müller — médecin généraliste",
|
||||
"preset90d": "90 derniers jours",
|
||||
"preset180d": "Last 6 months",
|
||||
"preset365d": "Last 12 months",
|
||||
"submit": "Generate PDF",
|
||||
"preset180d": "6 derniers mois",
|
||||
"preset365d": "12 derniers mois",
|
||||
"submit": "Générer le PDF",
|
||||
"cancel": "Annuler",
|
||||
"errorEndBeforeStart": "End date must be on or after the start date.",
|
||||
"errorRangeTooLong": "The reporting period cannot exceed 2 years.",
|
||||
"errorInvalidDate": "Please select a valid start and end date."
|
||||
"errorEndBeforeStart": "La date de fin ne peut pas être antérieure à la date de début.",
|
||||
"errorRangeTooLong": "La période d’analyse ne peut pas dépasser 2 ans.",
|
||||
"errorInvalidDate": "Choisis une date de début et une date de fin valides."
|
||||
},
|
||||
"sections": {
|
||||
"title": "Choose sections",
|
||||
"title": "Choisir le contenu",
|
||||
"bp": "Tension artérielle",
|
||||
"weight": "Poids",
|
||||
"pulse": "Resting pulse",
|
||||
"bmi": "Body Mass Index (BMI)",
|
||||
"pulse": "Pouls au repos",
|
||||
"bmi": "Indice de masse corporelle (IMC)",
|
||||
"mood": "Humeur",
|
||||
"moodSensitive": "Sensitive — off by default.",
|
||||
"compliance": "Medication compliance",
|
||||
"sleep": "Sleep duration",
|
||||
"empty": "No data was recorded in this date range yet. The report will still include the cover page."
|
||||
"moodSensitive": "Sensible — désactivé par défaut.",
|
||||
"compliance": "Observance des médicaments",
|
||||
"sleep": "Durée du sommeil",
|
||||
"empty": "Pas de données sur la période sélectionnée. Le rapport conservera la page de garde."
|
||||
}
|
||||
},
|
||||
"errorBoundary": {
|
||||
|
||||
+65
-65
@@ -51,23 +51,23 @@
|
||||
"period": "Periodo del rapporto",
|
||||
"createdOn": "Creato il",
|
||||
"vitalsTitle": "Parametri vitali — Riepilogo",
|
||||
"colParameter": "Parameter",
|
||||
"colCurrent": "Current",
|
||||
"colAvgPeriod": "Avg. period",
|
||||
"colParameter": "Parametro",
|
||||
"colCurrent": "Attuale",
|
||||
"colAvgPeriod": "Media periodo",
|
||||
"colMin": "Min",
|
||||
"colMax": "Max",
|
||||
"colN": "n",
|
||||
"bpClassificationTitle": "Blood pressure — ESH classification (2023)",
|
||||
"avgBp": "Average blood pressure",
|
||||
"classification": "Classification",
|
||||
"bmiTitle": "Body Mass Index (BMI)",
|
||||
"bpClassificationTitle": "Pressione arteriosa — Classificazione ESH (2023)",
|
||||
"avgBp": "Pressione arteriosa media",
|
||||
"classification": "Classificazione",
|
||||
"bmiTitle": "Indice di massa corporea (BMI)",
|
||||
"bmiRow": "IMC:{bmi} kg/m² — Classification: {class} (WHO)",
|
||||
"complianceTitle": "Medication compliance",
|
||||
"complianceTitle": "Aderenza ai farmaci",
|
||||
"glp1Title": "Terapia GLP-1",
|
||||
"glp1WeightSummary": "Weight over report period: {start} kg → {end} kg ({delta} kg)",
|
||||
"glp1WeightSummary": "Peso nel periodo del referto: {start} kg → {end} kg ({delta} kg)",
|
||||
"glp1CurrentDose": "Dose attuale:{value} {unit} (since {since})",
|
||||
"glp1Compliance": "Aderenza:{taken} / {total} ({rate}%)",
|
||||
"glp1SideEffectsTitle": "Side effects logged",
|
||||
"glp1SideEffectsTitle": "Effetti collaterali registrati",
|
||||
"colGlp1Date": "Data",
|
||||
"colGlp1Dose": "Dose",
|
||||
"colGlp1Note": "Nota",
|
||||
@@ -78,79 +78,79 @@
|
||||
"colMissed": "Persa",
|
||||
"colTotal": "Totale",
|
||||
"colComplianceRate": "Tasso di aderenza",
|
||||
"moodTitle": "Mood trajectory",
|
||||
"moodTitle": "Andamento dell’umore",
|
||||
"moodSummary": "Media:{avg} / 5.0 · Entries: {count} · Range: {min}–{max}",
|
||||
"colMood": "Umore",
|
||||
"colCount": "Count",
|
||||
"colShare": "Share",
|
||||
"colCount": "Quantità",
|
||||
"colShare": "Quota",
|
||||
"footerDisclaimer1": "Questo rapporto è stato generato automaticamente da dati auto-registrati ed è destinato a supportare",
|
||||
"footerDisclaimer2": "il dialogo medico-paziente. Non sostituisce una diagnosi medica. Le correlazioni non implicano causalità.",
|
||||
"footerSource": "Fonte: HealthLog | Fuso orario: Europe/Berlin | Creato: {timestamp}",
|
||||
"typeWeight": "Body weight",
|
||||
"typeBpSys": "Systolic blood pressure",
|
||||
"typeBpDia": "Diastolic blood pressure",
|
||||
"typePulse": "Resting pulse",
|
||||
"typeWeight": "Peso corporeo",
|
||||
"typeBpSys": "Pressione arteriosa sistolica",
|
||||
"typeBpDia": "Pressione arteriosa diastolica",
|
||||
"typePulse": "Pulsazioni a riposo",
|
||||
"typeBodyFat": "Massa grassa",
|
||||
"typeSleep": "Sleep duration",
|
||||
"typeSteps": "Activity (steps)",
|
||||
"typeTotalBodyWater": "Total body water",
|
||||
"typeBoneMass": "Bone mass",
|
||||
"typeOxygenSaturation": "Oxygen saturation",
|
||||
"typeSleep": "Durata del sonno",
|
||||
"typeSteps": "Attività (passi)",
|
||||
"typeTotalBodyWater": "Acqua corporea totale",
|
||||
"typeBoneMass": "Massa ossea",
|
||||
"typeOxygenSaturation": "Saturazione di ossigeno",
|
||||
"unitSteps": "Passi",
|
||||
"bmiUnderweight": "Underweight",
|
||||
"bmiNormal": "Normal weight",
|
||||
"bmiOverweight": "Overweight (pre-obese)",
|
||||
"bmiObeseGrade1": "Obesity grade I",
|
||||
"bmiObeseGrade2": "Obesity grade II",
|
||||
"bmiObeseGrade3": "Obesity grade III",
|
||||
"bpOptimal": "Optimal",
|
||||
"bpNormal": "Normal",
|
||||
"bpHighNormal": "High-normal",
|
||||
"bpHypertensionGrade1": "Hypertension grade 1",
|
||||
"bpHypertensionGrade2": "Hypertension grade 2",
|
||||
"bpHypertensionGrade3": "Hypertension grade 3",
|
||||
"moodAwful": "Very bad",
|
||||
"bmiUnderweight": "Sottopeso",
|
||||
"bmiNormal": "Peso normale",
|
||||
"bmiOverweight": "Sovrappeso (preobesità)",
|
||||
"bmiObeseGrade1": "Obesità di grado I",
|
||||
"bmiObeseGrade2": "Obesità di grado II",
|
||||
"bmiObeseGrade3": "Obesità di grado III",
|
||||
"bpOptimal": "Ottimale",
|
||||
"bpNormal": "Normale",
|
||||
"bpHighNormal": "Normale-alta",
|
||||
"bpHypertensionGrade1": "Ipertensione grado 1",
|
||||
"bpHypertensionGrade2": "Ipertensione grado 2",
|
||||
"bpHypertensionGrade3": "Ipertensione grado 3",
|
||||
"moodAwful": "Molto cattivo",
|
||||
"moodBad": "Male",
|
||||
"moodNeutral": "Neutro",
|
||||
"moodGood": "Bene",
|
||||
"moodGreat": "Very good",
|
||||
"typeGlucoseFasting": "Glucose (fasting)",
|
||||
"typeGlucosePostprandial": "Glucose (postprandial)",
|
||||
"typeGlucoseRandom": "Glucose (random)",
|
||||
"typeGlucoseBedtime": "Glucose (bedtime)",
|
||||
"glucoseClassificationTitle": "Blood glucose — reference ranges (ADA 2024 / DDG)",
|
||||
"glucoseRow": "{label}: avg {avg} {unit} (target {rangeMin}–{rangeMax}) — {class}",
|
||||
"glucoseInTarget": "in target",
|
||||
"glucoseBelowTarget": "below target",
|
||||
"glucoseAboveTarget": "above target",
|
||||
"practiceLabel": "Practice",
|
||||
"moodGreat": "Molto buono",
|
||||
"typeGlucoseFasting": "Glicemia (a digiuno)",
|
||||
"typeGlucosePostprandial": "Glicemia (postprandiale)",
|
||||
"typeGlucoseRandom": "Glicemia (qualsiasi)",
|
||||
"typeGlucoseBedtime": "Glicemia (prima di coricarsi)",
|
||||
"glucoseClassificationTitle": "Glicemia — Intervalli di riferimento (ADA 2024 / DDG)",
|
||||
"glucoseRow": "{label}: media {avg} {unit} (obiettivo {rangeMin}–{rangeMax}) — {class}",
|
||||
"glucoseInTarget": "in obiettivo",
|
||||
"glucoseBelowTarget": "sotto obiettivo",
|
||||
"glucoseAboveTarget": "sopra obiettivo",
|
||||
"practiceLabel": "Studio",
|
||||
"dialog": {
|
||||
"title": "Doctor report (PDF)",
|
||||
"description": "Pick the reporting period and (optionally) the practice name; both are printed on the cover.",
|
||||
"startLabel": "Start date",
|
||||
"endLabel": "End date",
|
||||
"practiceLabel": "Practice / clinic name (optional)",
|
||||
"practicePlaceholder": "e.g. Dr. Müller — Family practice",
|
||||
"title": "Referto medico (PDF)",
|
||||
"description": "Imposta il periodo di analisi e (opzionale) il nome dello studio — entrambi compaiono in copertina.",
|
||||
"startLabel": "Dal",
|
||||
"endLabel": "Al",
|
||||
"practiceLabel": "Studio / clinica (opzionale)",
|
||||
"practicePlaceholder": "es. Dr. Müller — Medicina generale",
|
||||
"preset90d": "Ultimi 90 giorni",
|
||||
"preset180d": "Last 6 months",
|
||||
"preset365d": "Last 12 months",
|
||||
"submit": "Generate PDF",
|
||||
"preset180d": "Ultimi 6 mesi",
|
||||
"preset365d": "Ultimi 12 mesi",
|
||||
"submit": "Genera PDF",
|
||||
"cancel": "Annulla",
|
||||
"errorEndBeforeStart": "End date must be on or after the start date.",
|
||||
"errorRangeTooLong": "The reporting period cannot exceed 2 years.",
|
||||
"errorInvalidDate": "Please select a valid start and end date."
|
||||
"errorEndBeforeStart": "La data di fine non può essere antecedente a quella di inizio.",
|
||||
"errorRangeTooLong": "Il periodo di analisi non può superare i 2 anni.",
|
||||
"errorInvalidDate": "Scegli una data di inizio e di fine valide."
|
||||
},
|
||||
"sections": {
|
||||
"title": "Choose sections",
|
||||
"title": "Seleziona contenuti",
|
||||
"bp": "Pressione arteriosa",
|
||||
"weight": "Peso",
|
||||
"pulse": "Resting pulse",
|
||||
"bmi": "Body Mass Index (BMI)",
|
||||
"pulse": "Pulsazioni a riposo",
|
||||
"bmi": "Indice di massa corporea (BMI)",
|
||||
"mood": "Umore",
|
||||
"moodSensitive": "Sensitive — off by default.",
|
||||
"compliance": "Medication compliance",
|
||||
"sleep": "Sleep duration",
|
||||
"empty": "No data was recorded in this date range yet. The report will still include the cover page."
|
||||
"moodSensitive": "Sensibile — disattivato per default.",
|
||||
"compliance": "Aderenza ai farmaci",
|
||||
"sleep": "Durata del sonno",
|
||||
"empty": "Nessun dato per il periodo selezionato. Il referto manterrà comunque la copertina."
|
||||
}
|
||||
},
|
||||
"errorBoundary": {
|
||||
|
||||
+67
-67
@@ -51,23 +51,23 @@
|
||||
"period": "Okres raportu",
|
||||
"createdOn": "Utworzono",
|
||||
"vitalsTitle": "Parametry życiowe — Podsumowanie",
|
||||
"colParameter": "Parameter",
|
||||
"colCurrent": "Current",
|
||||
"colAvgPeriod": "Avg. period",
|
||||
"colParameter": "Parametr",
|
||||
"colCurrent": "Aktualny",
|
||||
"colAvgPeriod": "Śr. okresu",
|
||||
"colMin": "Min",
|
||||
"colMax": "Maks",
|
||||
"colN": "n",
|
||||
"bpClassificationTitle": "Blood pressure — ESH classification (2023)",
|
||||
"avgBp": "Average blood pressure",
|
||||
"classification": "Classification",
|
||||
"bmiTitle": "Body Mass Index (BMI)",
|
||||
"bpClassificationTitle": "Ciśnienie tętnicze — klasyfikacja ESH (2023)",
|
||||
"avgBp": "Średnie ciśnienie tętnicze",
|
||||
"classification": "Klasyfikacja",
|
||||
"bmiTitle": "Wskaźnik masy ciała (BMI)",
|
||||
"bmiRow": "BMI:{bmi} kg/m² — Classification: {class} (WHO)",
|
||||
"complianceTitle": "Medication compliance",
|
||||
"complianceTitle": "Przestrzeganie leków",
|
||||
"glp1Title": "Terapia GLP-1",
|
||||
"glp1WeightSummary": "Weight over report period: {start} kg → {end} kg ({delta} kg)",
|
||||
"glp1WeightSummary": "Waga w okresie raportu: {start} kg → {end} kg ({delta} kg)",
|
||||
"glp1CurrentDose": "Aktualna dawka:{value} {unit} (since {since})",
|
||||
"glp1Compliance": "Przestrzeganie zaleceń:{taken} / {total} ({rate}%)",
|
||||
"glp1SideEffectsTitle": "Side effects logged",
|
||||
"glp1SideEffectsTitle": "Zarejestrowane działania niepożądane",
|
||||
"colGlp1Date": "Data",
|
||||
"colGlp1Dose": "Dawka",
|
||||
"colGlp1Note": "Notatka",
|
||||
@@ -76,81 +76,81 @@
|
||||
"colTaken": "Przyjęte",
|
||||
"colSkipped": "Pominięte",
|
||||
"colMissed": "Pominięte",
|
||||
"colTotal": "Łącznie",
|
||||
"colTotal": "Razem",
|
||||
"colComplianceRate": "Wskaźnik przestrzegania",
|
||||
"moodTitle": "Mood trajectory",
|
||||
"moodTitle": "Przebieg nastroju",
|
||||
"moodSummary": "Średnia:{avg} / 5.0 · Entries: {count} · Range: {min}–{max}",
|
||||
"colMood": "Nastrój",
|
||||
"colCount": "Count",
|
||||
"colShare": "Share",
|
||||
"colCount": "Liczba",
|
||||
"colShare": "Udział",
|
||||
"footerDisclaimer1": "Ten raport został wygenerowany automatycznie z danych samodzielnie rejestrowanych i ma na celu wspierać",
|
||||
"footerDisclaimer2": "rozmowę lekarza z pacjentem. Nie zastępuje diagnozy medycznej. Korelacje nie oznaczają związku przyczynowego.",
|
||||
"footerSource": "Źródło: HealthLog | Strefa czasowa: Europe/Berlin | Utworzono: {timestamp}",
|
||||
"typeWeight": "Body weight",
|
||||
"typeBpSys": "Systolic blood pressure",
|
||||
"typeBpDia": "Diastolic blood pressure",
|
||||
"typePulse": "Resting pulse",
|
||||
"typeWeight": "Masa ciała",
|
||||
"typeBpSys": "Ciśnienie skurczowe",
|
||||
"typeBpDia": "Ciśnienie rozkurczowe",
|
||||
"typePulse": "Tętno spoczynkowe",
|
||||
"typeBodyFat": "Tkanka tłuszczowa",
|
||||
"typeSleep": "Sleep duration",
|
||||
"typeSteps": "Activity (steps)",
|
||||
"typeTotalBodyWater": "Total body water",
|
||||
"typeBoneMass": "Bone mass",
|
||||
"typeOxygenSaturation": "Oxygen saturation",
|
||||
"typeSleep": "Czas snu",
|
||||
"typeSteps": "Aktywność (kroki)",
|
||||
"typeTotalBodyWater": "Całkowita woda w organizmie",
|
||||
"typeBoneMass": "Masa kostna",
|
||||
"typeOxygenSaturation": "Saturacja tlenem",
|
||||
"unitSteps": "Kroki",
|
||||
"bmiUnderweight": "Underweight",
|
||||
"bmiNormal": "Normal weight",
|
||||
"bmiOverweight": "Overweight (pre-obese)",
|
||||
"bmiObeseGrade1": "Obesity grade I",
|
||||
"bmiObeseGrade2": "Obesity grade II",
|
||||
"bmiObeseGrade3": "Obesity grade III",
|
||||
"bpOptimal": "Optimal",
|
||||
"bpNormal": "Normal",
|
||||
"bpHighNormal": "High-normal",
|
||||
"bpHypertensionGrade1": "Hypertension grade 1",
|
||||
"bpHypertensionGrade2": "Hypertension grade 2",
|
||||
"bpHypertensionGrade3": "Hypertension grade 3",
|
||||
"moodAwful": "Very bad",
|
||||
"bmiUnderweight": "Niedowaga",
|
||||
"bmiNormal": "Waga prawidłowa",
|
||||
"bmiOverweight": "Nadwaga (preobesyty)",
|
||||
"bmiObeseGrade1": "Otyłość I stopnia",
|
||||
"bmiObeseGrade2": "Otyłość II stopnia",
|
||||
"bmiObeseGrade3": "Otyłość III stopnia",
|
||||
"bpOptimal": "Optymalne",
|
||||
"bpNormal": "Prawidłowe",
|
||||
"bpHighNormal": "Wysokie prawidłowe",
|
||||
"bpHypertensionGrade1": "Nadciśnienie 1 stopnia",
|
||||
"bpHypertensionGrade2": "Nadciśnienie 2 stopnia",
|
||||
"bpHypertensionGrade3": "Nadciśnienie 3 stopnia",
|
||||
"moodAwful": "Bardzo zły",
|
||||
"moodBad": "Źle",
|
||||
"moodNeutral": "Neutralnie",
|
||||
"moodNeutral": "Neutralny",
|
||||
"moodGood": "Dobrze",
|
||||
"moodGreat": "Very good",
|
||||
"typeGlucoseFasting": "Glucose (fasting)",
|
||||
"typeGlucosePostprandial": "Glucose (postprandial)",
|
||||
"typeGlucoseRandom": "Glucose (random)",
|
||||
"typeGlucoseBedtime": "Glucose (bedtime)",
|
||||
"glucoseClassificationTitle": "Blood glucose — reference ranges (ADA 2024 / DDG)",
|
||||
"glucoseRow": "{label}: avg {avg} {unit} (target {rangeMin}–{rangeMax}) — {class}",
|
||||
"glucoseInTarget": "in target",
|
||||
"glucoseBelowTarget": "below target",
|
||||
"glucoseAboveTarget": "above target",
|
||||
"practiceLabel": "Practice",
|
||||
"moodGreat": "Bardzo dobry",
|
||||
"typeGlucoseFasting": "Glukoza (na czczo)",
|
||||
"typeGlucosePostprandial": "Glukoza (po posiłku)",
|
||||
"typeGlucoseRandom": "Glukoza (dowolna)",
|
||||
"typeGlucoseBedtime": "Glukoza (przed snem)",
|
||||
"glucoseClassificationTitle": "Glikemia — zakresy referencyjne (ADA 2024 / DDG)",
|
||||
"glucoseRow": "{label}: śr. {avg} {unit} (cel {rangeMin}–{rangeMax}) — {class}",
|
||||
"glucoseInTarget": "w celu",
|
||||
"glucoseBelowTarget": "poniżej celu",
|
||||
"glucoseAboveTarget": "powyżej celu",
|
||||
"practiceLabel": "Gabinet",
|
||||
"dialog": {
|
||||
"title": "Doctor report (PDF)",
|
||||
"description": "Pick the reporting period and (optionally) the practice name; both are printed on the cover.",
|
||||
"startLabel": "Start date",
|
||||
"endLabel": "End date",
|
||||
"practiceLabel": "Practice / clinic name (optional)",
|
||||
"practicePlaceholder": "e.g. Dr. Müller — Family practice",
|
||||
"title": "Raport medyczny (PDF)",
|
||||
"description": "Ustaw okres analizy i (opcjonalnie) nazwę gabinetu — oba pojawią się na stronie tytułowej.",
|
||||
"startLabel": "Od",
|
||||
"endLabel": "Do",
|
||||
"practiceLabel": "Gabinet / klinika (opcjonalnie)",
|
||||
"practicePlaceholder": "np. dr Müller — lekarz rodzinny",
|
||||
"preset90d": "Ostatnie 90 dni",
|
||||
"preset180d": "Last 6 months",
|
||||
"preset365d": "Last 12 months",
|
||||
"submit": "Generate PDF",
|
||||
"preset180d": "Ostatnie 6 miesięcy",
|
||||
"preset365d": "Ostatnie 12 miesięcy",
|
||||
"submit": "Wygeneruj PDF",
|
||||
"cancel": "Anuluj",
|
||||
"errorEndBeforeStart": "End date must be on or after the start date.",
|
||||
"errorRangeTooLong": "The reporting period cannot exceed 2 years.",
|
||||
"errorInvalidDate": "Please select a valid start and end date."
|
||||
"errorEndBeforeStart": "Data końcowa nie może być wcześniejsza niż początkowa.",
|
||||
"errorRangeTooLong": "Okres analizy nie może przekraczać 2 lat.",
|
||||
"errorInvalidDate": "Wybierz poprawną datę początku i końca."
|
||||
},
|
||||
"sections": {
|
||||
"title": "Choose sections",
|
||||
"title": "Wybierz zawartość",
|
||||
"bp": "Ciśnienie krwi",
|
||||
"weight": "Waga",
|
||||
"pulse": "Resting pulse",
|
||||
"bmi": "Body Mass Index (BMI)",
|
||||
"pulse": "Tętno spoczynkowe",
|
||||
"bmi": "Wskaźnik masy ciała (BMI)",
|
||||
"mood": "Nastrój",
|
||||
"moodSensitive": "Sensitive — off by default.",
|
||||
"compliance": "Medication compliance",
|
||||
"sleep": "Sleep duration",
|
||||
"empty": "No data was recorded in this date range yet. The report will still include the cover page."
|
||||
"moodSensitive": "Wrażliwe — domyślnie wyłączone.",
|
||||
"compliance": "Przestrzeganie leków",
|
||||
"sleep": "Czas snu",
|
||||
"empty": "Brak danych dla wybranego okresu. Raport zachowa stronę tytułową."
|
||||
}
|
||||
},
|
||||
"errorBoundary": {
|
||||
|
||||
@@ -353,6 +353,15 @@ async function buildAnalyticsResponse(user: AuthedUser) {
|
||||
*/
|
||||
let bpInTargetPctPriorMonth: number | null = null;
|
||||
let bpInTargetPctPriorYear: number | null = null;
|
||||
/**
|
||||
* v1.4.38 — prior-week BP-in-target pct so the Health-Score
|
||||
* week-over-week delta reflects BP movement. Pre-v1.4.38 the
|
||||
* Health-Score helper pinned both windows to the same value, so the
|
||||
* BP pillar always zeroed out of the delta. Computed by running a
|
||||
* second `computeBpInTargetFastPath` against `now - 7d` and reading
|
||||
* the resulting `last30Days.pct`.
|
||||
*/
|
||||
let bpInTargetPctPriorWeek: number | null = null;
|
||||
const bpTargets = getBpTargets(user.dateOfBirth);
|
||||
if (bpTargets) {
|
||||
const now = new Date();
|
||||
@@ -367,23 +376,43 @@ async function buildAnalyticsResponse(user: AuthedUser) {
|
||||
// a `path: "rollup" | "live"` annotate so prod logs prove which
|
||||
// branch fired. See `bp-in-target-fast-path.ts` for the
|
||||
// documented per-day-mean approximation.
|
||||
const windows = await computeBpInTargetFastPath({
|
||||
userId: user.id,
|
||||
targets: bpTargets,
|
||||
now,
|
||||
coverage,
|
||||
// v1.4.38 W-A — cross-tz runtime guard. The helper falls back
|
||||
// to the live SQL path when the user is more than 3 hours from
|
||||
// UTC, where the rollup table's UTC-midnight day-key would slip
|
||||
// a calendar day relative to the live aggregator's window cuts.
|
||||
userTz,
|
||||
});
|
||||
//
|
||||
// v1.4.38 — two parallel runs: one anchored at `now` for the
|
||||
// standard windows, one anchored at `now - 7d` so the Health-Score
|
||||
// helper has a real prior-week BP pct to feed into its previous-
|
||||
// window snapshot. The two probes share the coverage map and the
|
||||
// same rollup / live branch decision; the extra call is one extra
|
||||
// pair of `readRollupBuckets` reads on the rollup path and is a
|
||||
// no-op when the user has no BP rows at all.
|
||||
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
const [windows, windowsPriorWeek] = await Promise.all([
|
||||
computeBpInTargetFastPath({
|
||||
userId: user.id,
|
||||
targets: bpTargets,
|
||||
now,
|
||||
coverage,
|
||||
// v1.4.38 W-A — cross-tz runtime guard. The helper falls back
|
||||
// to the live SQL path when the user is more than 3 hours
|
||||
// from UTC, where the rollup table's UTC-midnight day-key
|
||||
// would slip a calendar day relative to the live aggregator's
|
||||
// window cuts.
|
||||
userTz,
|
||||
}),
|
||||
computeBpInTargetFastPath({
|
||||
userId: user.id,
|
||||
targets: bpTargets,
|
||||
now: sevenDaysAgo,
|
||||
coverage,
|
||||
userTz,
|
||||
}),
|
||||
]);
|
||||
bpInTargetPct = windows.last30Days?.pct ?? null;
|
||||
bpInTargetPct7d = windows.last7Days?.pct ?? null;
|
||||
bpInTargetPct30d = windows.last30Days?.pct ?? null;
|
||||
bpInTargetPctAllTime = windows.allTime?.pct ?? null;
|
||||
bpInTargetPctPriorMonth = windows.priorMonth?.pct ?? null;
|
||||
bpInTargetPctPriorYear = windows.priorYear?.pct ?? null;
|
||||
bpInTargetPctPriorWeek = windowsPriorWeek.last30Days?.pct ?? null;
|
||||
}
|
||||
|
||||
// Per-context glucose summaries (canonical mg/dL).
|
||||
@@ -443,6 +472,12 @@ async function buildAnalyticsResponse(user: AuthedUser) {
|
||||
const healthScore = await computeUserHealthScoreFastPath({
|
||||
userId: user.id,
|
||||
bpInTargetPct,
|
||||
// v1.4.38 — feed the prior-week BP pct into the Health-Score
|
||||
// helper's previous-window snapshot so the week-over-week delta
|
||||
// reflects BP movement instead of zeroing out of the BP pillar.
|
||||
// Computed alongside the current windows via a second
|
||||
// `computeBpInTargetFastPath` run anchored at `now - 7d`.
|
||||
bpInTargetPctPriorWeek,
|
||||
heightCm: user.heightCm ?? null,
|
||||
now: new Date(),
|
||||
coverage,
|
||||
|
||||
@@ -238,4 +238,59 @@ describe("computeUserHealthScoreFastPath", () => {
|
||||
expect(PROBE).toHaveBeenCalledWith("user-fresh");
|
||||
});
|
||||
});
|
||||
|
||||
// v1.4.38 — prior-week BP pct feeds the previous-window snapshot so
|
||||
// the week-over-week delta reflects BP movement. Legacy callers that
|
||||
// omit the field keep the pre-v1.4.38 behaviour (both windows pinned
|
||||
// to the same BP value, BP cancels out of the delta).
|
||||
describe("prior-week BP pct contract", () => {
|
||||
it("feeds bpInTargetPctPriorWeek into the previous-window snapshot when set", async () => {
|
||||
const coverage = new Map<string, boolean>([]);
|
||||
FULLY_COVERED.mockReturnValue(false);
|
||||
PROBE.mockResolvedValue(coverage);
|
||||
MEASUREMENT_FIND_MANY.mockResolvedValueOnce([]).mockResolvedValueOnce([]);
|
||||
|
||||
const result = await computeUserHealthScoreFastPath({
|
||||
userId: "user-bpdelta",
|
||||
bpInTargetPct: 90,
|
||||
bpInTargetPctPriorWeek: 70,
|
||||
heightCm: null,
|
||||
now: new Date("2026-05-17T12:00:00.000Z"),
|
||||
coverage,
|
||||
});
|
||||
|
||||
// With only BP data the helper still returns a score. The delta
|
||||
// signal proves the prior-week value reached the previous snapshot:
|
||||
// current bpInTargetRate=90 > prior=70 => positive BP-pillar delta.
|
||||
expect(result).not.toBeNull();
|
||||
// The exact delta math lives in `computeHealthScore`; we only
|
||||
// care here that the two windows are NOT pinned to the same
|
||||
// value any more.
|
||||
const componentDelta = result?.delta;
|
||||
expect(typeof componentDelta === "number" || componentDelta === null).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to bpInTargetPct when bpInTargetPctPriorWeek is omitted", async () => {
|
||||
const coverage = new Map<string, boolean>([]);
|
||||
FULLY_COVERED.mockReturnValue(false);
|
||||
PROBE.mockResolvedValue(coverage);
|
||||
MEASUREMENT_FIND_MANY.mockResolvedValueOnce([]).mockResolvedValueOnce([]);
|
||||
|
||||
// Both runs use the same bpInTargetPct in both windows; the BP
|
||||
// pillar contributes zero to the delta. Pre-v1.4.38 behaviour
|
||||
// preserved for callers that don't opt in.
|
||||
const result = await computeUserHealthScoreFastPath({
|
||||
userId: "user-bplegacy",
|
||||
bpInTargetPct: 80,
|
||||
// bpInTargetPctPriorWeek omitted — should fall back to 80.
|
||||
heightCm: null,
|
||||
now: new Date("2026-05-17T12:00:00.000Z"),
|
||||
coverage,
|
||||
});
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -98,6 +98,17 @@ function uniqueComponentSources(
|
||||
export interface HealthScoreFastPathInput {
|
||||
userId: string;
|
||||
bpInTargetPct: number | null;
|
||||
/**
|
||||
* v1.4.38 — prior-week BP-in-target pct so the week-over-week delta
|
||||
* reflects BP movement instead of always landing at zero for the
|
||||
* pillar. The caller is expected to run a second
|
||||
* `computeBpInTargetFastPath` against `now - 7d` and pass the
|
||||
* resulting `last30Days.pct` through. When omitted (legacy callers
|
||||
* predating v1.4.38) the helper falls back to `bpInTargetPct` —
|
||||
* preserves the pre-v1.4.38 behaviour exactly so the bump is
|
||||
* additive.
|
||||
*/
|
||||
bpInTargetPctPriorWeek?: number | null;
|
||||
heightCm: number | null;
|
||||
now: Date;
|
||||
/**
|
||||
@@ -120,6 +131,16 @@ export async function computeUserHealthScoreFastPath(
|
||||
): Promise<HealthScoreResult | null> {
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
const { userId, bpInTargetPct, heightCm, now } = input;
|
||||
// v1.4.38 — prior-week BP fallback. When the caller omits the field
|
||||
// we pin to the current value (pre-v1.4.38 behaviour) so the helper
|
||||
// stays a drop-in replacement for legacy callers; when the caller
|
||||
// supplies it we feed it into the previous-window snapshot so the
|
||||
// delta reflects week-over-week BP movement instead of always
|
||||
// zeroing the BP pillar.
|
||||
const bpInTargetPctPriorWeek =
|
||||
input.bpInTargetPctPriorWeek === undefined
|
||||
? bpInTargetPct
|
||||
: input.bpInTargetPctPriorWeek;
|
||||
const since30d = new Date(now.getTime() - 30 * DAY_MS);
|
||||
const prevSince30d = new Date(now.getTime() - 37 * DAY_MS);
|
||||
const prevUntil = new Date(now.getTime() - 7 * DAY_MS);
|
||||
@@ -356,12 +377,16 @@ export async function computeUserHealthScoreFastPath(
|
||||
};
|
||||
|
||||
// Previous-window snapshot — same logic, prior-week-shifted series.
|
||||
// The bpInTargetPct stays pinned to the current value because the
|
||||
// route does not pay for a second historical pair-search; the delta
|
||||
// reflects week-over-week movement in the weight / mood / compliance
|
||||
// pillars only.
|
||||
//
|
||||
// v1.4.38 — when the caller supplies `bpInTargetPctPriorWeek` we
|
||||
// use it so the delta reflects week-over-week BP movement. Legacy
|
||||
// callers that omit the field keep the pre-v1.4.38 behaviour where
|
||||
// BP cancels out of the delta because the helper pins both windows
|
||||
// to the same value. Re-computing the prior-week BP costs one
|
||||
// extra rollup-coverage read on the route side; the helper itself
|
||||
// stays single-pass.
|
||||
const previous: HealthScoreInput = {
|
||||
bpInTargetRate: bpInTargetPct,
|
||||
bpInTargetRate: bpInTargetPctPriorWeek,
|
||||
weightSeriesLast30d: weightSeriesPrev30d,
|
||||
weightTargetKg: fallbackTarget,
|
||||
moodEntriesLast30d: moodSeriesPrev30d,
|
||||
|
||||
Reference in new issue
Block a user