Crise, forme et conseils
Le risque de crise, la baisse de forme, la forme du jour, les alertes et « Que faire maintenant ? ».
Chaque algorithme est décrit avec ce qu’il lit, ses étapes, les valeurs qu’il utilise (avec leur emplacement dans le code), ce qu’il affiche et ses limites.
Alertes de risque de crise et limite de notification
Si tu l'as activée, Trévor t'écrit chaque matin vers 8 h quand ton risque de crise atteint 70 % sur des mesures fraîches. Une alerte douce facultative existe dès 40 %. Il n'envoie jamais plus d'un message d'alerte par jour, et il retient le message pendant les heures calmes.
Où tu le vois : Message du bot reçu le matin (un message neutre avec un bouton « Ouvrir » sur l'écran verrouillé, puis le contenu), suivi d'un second message « Deux conseils pour maintenant ». Les boutons « Activer l'alerte du matin (si risque ≥ 70) » et « Me prévenir dès le risque modéré (40 %) » sont dans l'écran « Mon risque de crise ». Les soignants voient aussi une ligne d'alerte « Risque de crise calculé X % » dans le tableau de bord quand le score est de 70 ou plus.
Ce que l’algorithme lit
- Le risque de crise du jour (score, fraîcheur, explications), calculé comme sur la page publique du risque de crise (montre, déclaration, dossier)
- Ton choix d'activer l'alerte du matin, et ton choix d'activer l'alerte douce dès 40 % (réglage)
- Date de la dernière alerte envoyée (état de Trévor)
- Heures calmes (par défaut de 22 h à 7 h, heure locale) et fuseau horaire (réglage)
Le calcul, étape par étape
Les valeurs utilisées
| Nom dans le code | Valeur | Rôle | Où |
|---|---|---|---|
CRISIS_ALERT_THRESHOLD | 70 | Score à partir duquel l'alerte « élevé » part | src/trevor/wearables/service.py:511 |
CRISIS_SOFT_THRESHOLD | 40 | Score à partir duquel l'alerte douce part, si tu l'as activée | src/trevor/wearables/service.py:512 |
heure d'envoi | 8 h locales | Le planificateur lance les alertes de risque quand l'heure locale du groupe de fuseau vaut 8 | src/trevor/conversation/reminders.py:808-810 |
last_alert_date | une fois par jour | Si la date de la dernière alerte égale la date du jour de la personne, aucun nouvel envoi | src/trevor/wearables/service.py:1257, 1272 |
fraîcheur | dernière mesure datée d'au plus 2 jours | Sans mesure fraîche, aucun message. Les scores calculés sans montre (dossier seul) comptent comme frais | src/trevor/wearables/service.py:736, 763, 1263 |
alertes éteintes par défaut | alerts_enabled faux, soft_alert faux | Il faut activer l'alerte du matin, puis séparément l'alerte douce | src/trevor/wearables/service.py:1179-1184 ; src/trevor/navigator/reco_flow.py:52-55 |
nombre de messages | 2 au plus | Le message d'alerte et le message des deux conseils (omis si aucun geste n'est disponible) | src/trevor/conversation/reminders.py:1544-1551 |
heures calmes par défaut | 22 h (inclus) à 7 h (exclu), heure locale | Fenêtre pendant laquelle les messages proactifs sont retenus. Tu peux la régler ou la couper | src/trevor/localtime.py:182, 214-226 |
QUIET_DAILY | inclut crisis_alerts | Une alerte retenue la nuit est abandonnée si le jour local a changé au réveil | src/trevor/conversation/reminders.py:99-103 |
budget d'attention | crisis_alerts exemptée | La limite commune de messages proactifs par jour (3 par défaut, si la fonction est activée) ne retient ni ne compte les alertes de crise | src/trevor/conversation/attention_budget.py:26, 44-48 |
mode silencieux d'étude | aucune alerte pendant une étude LRH | Pendant l'étude TREVOR-PILOTE, le risque est calculé et enregistré mais jamais affiché ni alerté | src/trevor/wearables/service.py:1186-1190 |
ligne du tableau de bord | score ≥ 70 et frais | Pour les soignants : « Risque de crise calculé X % (modèle …) » avec 3 explications au plus | src/trevor/dashboard/data.py:653-655 |
Ce qui est affiché
Au plus un message d'alerte par jour. Alerte « élevé » (score de 70 ou plus) : « Risque de crise ce matin : X % (élevé). Signaux : … », puis des gestes (boire environ 1,5 à 2 litres, se reposer au chaud, éviter les efforts intenses) et les cas où contacter l'équipe soignante ou appeler le 144 (douleur qui monte, fièvre au seuil, essoufflement). Alerte douce (de 40 à 69, si activée) : « Ton risque de crise monte un peu : X % ce matin (modéré). Signaux : … », avec la phrase « Rien d'alarmant à ce stade ». Les deux messages se terminent par « Cette fonction est expérimentale et ne vaut pas avis médical ». Le second message donne les deux premiers gestes de « Que faire maintenant ? ».
Un exemple chiffré
Score du matin à 72 %, mesures datées d'hier, alerte du matin activée, aucune alerte envoyée aujourd'hui : le score atteint 70, le message « élevé » part, et la date du jour est enregistrée. À 8 h 30, aucun second message ne part même si le score monte à 85. Autre cas : 45 % sans alerte douce activée, aucun message. Avec l'alerte douce activée, le message doux part. Autre cas : 75 % mais mesures de 4 jours, rien ne part. Aucun résultat chiffré n'a été exécuté : les cas viennent des conditions de due_alerts.
Ce que cet algorithme ne sait pas faire
- La date du jour est enregistrée avant que le message soit livré. Si l'envoi échoue ou si le message est abandonné (nuit puis jour local changé), tu ne reçois pas d'alerte ce jour-là, et aucune autre ne part.
- Le contrôle ne se fait qu'à 8 h locales. Un score qui dépasse 70 à 15 h attend le lendemain matin.
- Les seuils d'alerte (70 et 40) ne sont pas ceux des niveaux affichés : l'écran du risque écrit « modéré » dès 30 et « élevé » dès 60. Un score de 65 est donc « élevé » à l'écran, mais ne déclenche pas l'alerte de 70. Le texte de l'alerte dit pourtant « (élevé) » pour tout score de 70 ou plus.
- L'alerte est facultative et éteinte par défaut. Sans l'avoir activée, tu ne reçois rien, quel que soit le score.
- Si les mesures ont plus de 2 jours, aucun message ne part, même avec un score élevé, car le score est jugé périmé. L'absence d'alerte ne prouve donc pas l'absence de risque.
- Un score bas n'exclut jamais une crise. Les messages le rappellent dans « Que faire maintenant ? ».
- La nuit, l'alerte attend la fin des heures calmes. Une crise qui se prépare pendant la nuit n'est pas signalée plus tôt par Trévor : en cas d'urgence, il faut appeler le 144 sans attendre.
Code : src/trevor/wearables/service.py (CRISIS_ALERT_THRESHOLD, CRISIS_SOFT_THRESHOLD, due_alerts, alerts_enabled, set_alerts, silent_mode, interpretation_source), src/trevor/conversation/reminders.py (crisis_alerts, _deliver, QUIET_DAILY, tick à 8 h), src/trevor/conversation/push_gate.py (hold_if_quiet, push_gated), src/trevor/conversation/attention_budget.py (EXEMPT), src/trevor/localtime.py (is_quiet, QUIET_DEFAULT), src/trevor/navigator/reco_flow.py (soft_alert_enabled, alert_lines), src/trevor/dashboard/data.py (wearable_alerts), data/i18n/fr.json (textCrisisAlertMessage, textCrisisAlertSoft, textCrisisAlertsEnabled, textCrisisAlertsSoftEnabled)
Apprentissage du modèle personnel et de la cohorte
Chaque nuit, Trévor peut entraîner un modèle de régression logistique sur tes propres jours, puis un modèle commun à toutes les personnes suivies. Les poids de départ viennent d'un a priori clinique, et l'entraînement ne les éloigne que si tes données le justifient. Au moment du calcul, les modèles qui ont fait leurs preuves se mélangent avec l'a priori.
Où tu le vois : Dans l'écran « Mon risque de crise », le bloc « Fiabilité du modèle » dit si le calcul part de repères cliniques, de la cohorte ou de tes données (nombre de jours, nombre d'épisodes de crise, AUROC en validation temporelle).
Ce que l’algorithme lit
- Écarts à ta normale et repères du jour, pour chaque jour passé qui a une ligne de base (voir « Lignes de base et écarts ») (montre, déclaration)
- Étiquettes de crise : douleur déclarée de 7/10 ou plus, hospitalisation, observation de conversation d'intensité 7 ou plus qui parle de crise ou d'urgences, jours des épisodes du journal des crises (déclaration)
- Traitements tels qu'ils étaient chaque jour d'entraînement : temps écoulé depuis la transfusion, nombre de culots, régularité de l'hydroxyurée (dossier, déclaration)
Le calcul, étape par étape
Les valeurs utilisées
| Nom dans le code | Valeur | Rôle | Où |
|---|---|---|---|
jours d'entraînement | 180 | Historique relu pour entraîner (training_rows, days=180) | src/trevor/wearables/service.py:617 |
seuil de douleur de crise | pain_intensity ≥ 7 | Un jour est étiqueté crise quand la douleur déclarée du suivi atteint 7/10, ou si une hospitalisation est déclarée | src/trevor/wearables/service.py:553 |
observation de conversation | intensité ≥ 7 et mot parmi crise, urgence, hospital, cvo, vaso | Une observation de conversation compte comme crise si son intensité est de 7 ou plus et si son texte contient un de ces mots | src/trevor/wearables/service.py:513, 568-572 |
journal des crises | chaque jour d'un épisode déclaré | Chaque jour entre le début et la fin d'un épisode déclaré (jusqu'à aujourd'hui si en cours) est une crise certaine | src/trevor/wearables/service.py:573-587 |
HORIZON_DAYS | 2 | Le jour est positif si une crise survient aujourd'hui, demain ou après-demain (boucle de 0 à 2 jours inclus) | src/trevor/wearables/crisis.py:123, 413-419 |
minimum du modèle personnel | 60 jours et 4 crises | En dessous, train_user ne produit rien | src/trevor/wearables/service.py:662 |
minimum du modèle de cohorte | 60 jours et 5 crises | En dessous, train_cohort ne produit rien | src/trevor/wearables/service.py:677 |
l2 | 0,5 | Force du rappel de chaque poids vers sa valeur a priori (le biais n'est pas rappelé) | src/trevor/wearables/crisis.py:433, 453 |
epochs / lr | 400 tours ; pas 0,05 (200 tours en validation) | Descente de gradient sur l'ensemble des jours, sans bibliothèque externe | src/trevor/wearables/crisis.py:433, 482 |
pos_weight | (jours sans crise) / (jours de crise) | Les jours de crise comptent davantage dans le gradient pour compenser leur rareté | src/trevor/wearables/crisis.py:442-448 |
validation croisée | 4 blocs, au moins 30 jours et 3 crises | Blocs temporels contigus ; sans ces minimums, l'AUROC reste vide | src/trevor/wearables/crisis.py:470-485 |
seuil d'usage du modèle personnel | n_events ≥ 4 et AUROC ≥ 0,65 | Sans AUROC calculée, le modèle n'est pas retenu | src/trevor/wearables/crisis.py:508 |
seuil d'usage du modèle de cohorte | n_events ≥ 5 et AUROC ≥ 0,6 | Seuil d'AUROC de la cohorte, plus bas que celui du modèle personnel | src/trevor/wearables/crisis.py:506 |
poids de mélange | a priori 1 ; cohorte min(4 ; crises/5) ; personnel min(6 ; crises/2) | Plus il y a de crises observées, plus le modèle pèse, jusqu'à un plafond | src/trevor/wearables/crisis.py:505-509 |
RISK_GAIN | 1,6 | Multiplicateur appliqué à tous les poids du calcul, sauf hu_protect, dmt_other, tx_recent et tx_protect | src/trevor/wearables/crisis.py:494-500 |
PRIOR_BIAS | −2,2 | Environ 10 % de risque de base quand tout est normal | src/trevor/wearables/crisis.py:120 |
RISK_MAX | 0,99 | Le risque ne dépasse jamais 99 % | src/trevor/wearables/crisis.py:493 |
Les poids
Un poids positif augmente le résultat, un poids négatif le diminue.
| Signal | Poids | Où |
|---|---|---|
| Douleur déclarée récente (a priori) | +0,9 | src/trevor/wearables/crisis.py:83 |
| Fièvre déclarée (a priori) | +0,8 | src/trevor/wearables/crisis.py:90 |
| Difficultés à respirer déclarées (a priori) | +1,0 | src/trevor/wearables/crisis.py:90 |
| SpO2 sous 92 % (a priori) | +0,8 | src/trevor/wearables/crisis.py:83 |
| SpO2 vs ta normale (a priori) | −0,6 | src/trevor/wearables/crisis.py:81 |
| FC repos vs ta normale (a priori) | +0,55 | src/trevor/wearables/crisis.py:81 |
| HRV vs ta normale (a priori) | −0,45 | src/trevor/wearables/crisis.py:81 |
| Crise dans les 7 derniers jours (a priori) | +0,6 | src/trevor/wearables/crisis.py:83 |
| Transfusion récente (a priori, non amplifié) | −3,0 | src/trevor/wearables/crisis.py:78 |
| Transfusion qui tient encore (a priori, non amplifié) | −2,0 | src/trevor/wearables/crisis.py:78 |
| Effet de la transfusion qui s'estompe (a priori) | +1,4 | src/trevor/wearables/crisis.py:78 |
| Transfusion en retard (a priori) | +0,8 | src/trevor/wearables/crisis.py:93 |
| Creux d'oxygène de la nuit en valeur absolue (a priori) | +0,7 | src/trevor/wearables/crisis.py:97 |
| Hémoglobine basse (a priori) | +0,5 | src/trevor/wearables/crisis.py:93 |
Ce qui est affiché
Deux modèles enregistrés (le tien et celui de la cohorte), chacun avec ses poids, son biais, son nombre de jours, son nombre de crises et son AUROC en validation croisée temporelle. Au moment du calcul, la liste des modèles retenus et leurs poids de mélange. L'écran affiche l'origine du calcul : « repères cliniques » (a priori seul), « ajusté sur la cohorte » ou « ajusté sur tes données », avec jours, épisodes et AUROC.
Un exemple chiffré
Un modèle personnel avec 8 crises et une AUROC de 0,70, et un modèle de cohorte avec 10 crises et une AUROC de 0,62. Les deux passent leur seuil (4 crises et 0,65 ; 5 crises et 0,6). Les poids de mélange sont a priori 1,0, cohorte min(4 ; 10/5) = 2,0 et personnel min(6 ; 8/2) = 4,0, soit une part de 4/7 pour le modèle personnel. Si l'AUROC personnelle n'était que de 0,60, le modèle personnel serait écarté et seuls l'a priori (1,0) et la cohorte (2,0) resteraient. Vérifié en exécutant blend() du dépôt (résultat : prior 1.0, cohort 2.0, personal 4.0).
Ce que cet algorithme ne sait pas faire
- Le modèle apprend à prédire un jour étiqueté crise dans les 3 jours (aujourd'hui et les 2 suivants), alors que les textes parlent de 48 heures. L'étiquette est large : aujourd'hui, demain et après-demain.
- L'étiquette de crise vient en grande partie de ce que tu déclares. Une crise non déclarée compte comme un jour calme, et une douleur de 7/10 sans crise réelle compte comme une crise.
- Avec 4 à 10 crises, le modèle est fragile : l'AUROC est estimée sur très peu de cas, et un seul épisode de plusieurs jours compte pour beaucoup de jours positifs.
- L'AUROC affichée vient de la validation croisée, tandis que le modèle enregistré est entraîné sur tous les jours. Les blocs consécutifs d'un même épisode de crise peuvent aussi tomber de part et d'autre d'une frontière de bloc.
- À l'entraînement, la vasoconstriction vaut 0, et les traitements sont reconstitués jour par jour. En direct, ces valeurs sont lues à l'instant du calcul : le modèle n'a donc jamais appris le poids de la vasoconstriction sur tes données.
- Les poids appris sont ensuite multipliés par 1,6 sans nouvelle validation de ce gain, calibré sur un cas rapporté.
- Le modèle de cohorte regroupe des personnes très différentes, et ses écarts sont déjà normalisés par personne. Il peut être entraîné sur peu de personnes.
Code : src/trevor/wearables/crisis.py (build_features, train_logistic, auc_score, evaluate_blocked, blend, amplified, PRIOR_WEIGHTS), src/trevor/wearables/service.py (day_records, training_rows, _treatment_history, train_user, train_cohort, train_everyone, _stored_model)
Forme du jour (récupération)
La forme du jour est un repère de récupération sur 100, à la manière d'un score d'Oura. Trévor affiche tel quel le score que donne ton appareil quand il existe. Sinon il calcule le sien à partir de ton sommeil, de ta variabilité cardiaque (HRV) et de ton cœur au repos, comparés à ta normale.
Où tu le vois : Écran « Ma forme du jour » (commande /forme, ou Ma santé > Forme, risque, hémoglobine), tuile « Forme du jour » de Ma santé, écran Mesures et estimations, écran Ma santé de la montre. Le texte donne un pourcentage, un niveau (bonne, moyenne, basse), la source (score de l'appareil ou estimation Trévor), jusqu'à 2 facteurs, un conseil du jour et la mention que ce n'est pas un avis médical.
Ce que l’algorithme lit
- Score de récupération fourni par l'appareil (Oura readiness, Body Battery de Garmin, etc.) (montre ou bague)
- Minutes de sommeil, HRV, fréquence cardiaque au repos des 30 jours précédents et du jour (montre ou bague)
- Nuit déclarée dans « Ma nuit » : bonne, moyenne ou mauvaise (déclaration)
- Interprétation activée par la personne (avertissement accepté, réglage global ou programme de test) (réglage)
Le calcul, étape par étape
Les valeurs utilisées
| Nom dans le code | Valeur | Rôle | Où |
|---|---|---|---|
WEIGHTS | sommeil 0,40 ; HRV 0,35 ; FC repos 0,25 | Poids des trois composantes du score calculé (somme 1) | src/trevor/wearables/readiness.py:27 |
BASELINE_DAYS | 30 | Jours précédents qui forment la ligne de base | src/trevor/wearables/readiness.py:23 |
FRESH_DAYS | 2 | Âge maximal d'un score calculé pour être affiché ; un score d'appareil plus ancien reste affiché mais non « frais » | src/trevor/wearables/readiness.py:24, 88 |
séries lues | 35 jours | Longueur de la série passée à compute_readiness | src/trevor/wearables/service.py:1199 |
bornes du z | −2 à +2 | Chaque écart est limité à ±2 avant la moyenne | src/trevor/wearables/readiness.py:86 |
pente du score | 50 + moyenne × 15 | Une moyenne de +2 donne 80 ; −2 donne 20 | src/trevor/wearables/readiness.py:87 |
niveau bon / moyen / bas | ≥ 66 / 40 à 65 / < 40 | Seuils des niveaux | src/trevor/wearables/readiness.py:36-37 |
nombre minimal de composantes | 2 | Avec moins de deux mesures ce jour-là, le jour est écarté (corrigé en 2.200.2 : avant, un jour sans mesure donnait 50 %) | src/trevor/wearables/readiness.py:55-57, 83 |
MIN_BASELINE | 7 | Jours minimaux avant le jour choisi, et valeurs minimales par mesure | src/trevor/wearables/crisis.py:122 |
SELF_SLEEP_Z | bonne +1,0 ; moyenne 0,0 ; mauvaise −1,0 | Valeur donnée à la nuit déclarée quand la montre n'a pas mesuré le sommeil | src/trevor/navigator/sleep.py:26 |
seuil d'affichage d'un facteur | |z| ≥ 0,5, 2 facteurs au plus | Un facteur plus faible n'est pas nommé | src/trevor/wearables/readiness.py:96-101 |
Les poids
Un poids positif augmente le résultat, un poids négatif le diminue.
| Signal | Poids | Où |
|---|---|---|
| Sommeil plus long que ta normale | +0,40 | src/trevor/wearables/readiness.py:27 |
| HRV plus haute que ta normale | +0,35 | src/trevor/wearables/readiness.py:27 |
| FC au repos plus basse que ta normale | +0,25 | src/trevor/wearables/readiness.py:27, 80 |
Ce qui est affiché
Un score de 0 à 100 (en pourcentage), un niveau (bonne à partir de 66, moyenne de 40 à 65, basse sous 40), une source (« device » ou « computed »), jusqu'à 2 facteurs en phrases (par exemple « Nuit plus courte que d'habitude », « Ton cœur au repos est un peu élevé »), la date et l'indication « frais ». Le conseil dépend du niveau : bonne (belle forme, garde tes bons réflexes), moyenne (à ton rythme, hydrate-toi, garde de la marge), basse (repose-toi, hydrate-toi, ménage tes efforts, évite le froid). Le texte précise que ce n'est pas un avis médical et ne remplace ni ton ressenti ni ton équipe de soins. Quand rien ne s'affiche, une phrase donne la cause : pas de montre, mode silencieux, avertissement à accepter, moins de 7 jours de référence, mesures trop anciennes ou trop peu nombreuses.
Un exemple chiffré
Vingt jours de référence : sommeil autour de 430 min (médiane 430, dispersion 30 par le plancher), HRV médiane 51,5 (dispersion 4), FC au repos médiane 60,5 (dispersion 2). Aujourd'hui : sommeil 360 min, HRV 44, FC 65. Les z sont −2,33 (borné −2), −1,875 et +2,25 pour la FC, inversé en −2,25 (borné −2). La moyenne pondérée vaut (0,40 × −2 + 0,35 × −1,875 + 0,25 × −2) / 1 = −1,96. Score = 50 + (−1,96 × 15) = 20,7, arrondi à 21 : niveau bas. Les deux facteurs affichés sont le sommeil (|z| 2,33) puis la FC (|z| 2,25) : « Nuit plus courte que d'habitude » et « Ton cœur au repos est un peu élevé ». Résultat vérifié en exécutant compute_readiness du dépôt.
Ce que cet algorithme ne sait pas faire
- Le score de l'appareil est affiché tel quel, sans contrôle ni explication de Trévor. Seul le score calculé est expliqué.
- Le score calculé n'utilise que trois mesures. Il ignore la douleur, l'humeur, l'oxygène, la température et ce que tu déclares dans le suivi du jour. Une journée de forte douleur peut donc donner une forme « bonne » si tu as bien dormi.
- Quand une seule mesure est présente, il n'y a pas de score. Avec deux mesures seulement, le score repose sur elles deux avec des poids ramenés à 1.
- La nuit déclarée remplace le sommeil seulement le jour où la montre n'a pas mesuré le sommeil, avec le poids du sommeil (0,40).
- Le score est borné par des z de ±2, donc un score calculé reste entre 20 et 80 ; il n'atteint jamais 0 ni 100. Seul un score d'appareil peut aller jusqu'aux extrêmes.
- Un score d'appareil ancien (plus de 2 jours) n'est plus affiché comme forme du jour, mais un score calculé ancien est retiré. Un jour sans appareil ni calcul n'a pas de forme.
- La forme du jour est distincte du risque de baisse de forme et du « forme » de « Que faire maintenant ? ». Ce sont trois notions différentes avec des calculs différents.
Code : src/trevor/wearables/readiness.py (compute_readiness, _usable, _level, WEIGHTS, DRIVER_LABEL), src/trevor/wearables/service.py (readiness_today), src/trevor/conversation/wearables_screen.py (_readiness_lines, screen_readiness), src/trevor/lifecourse/health_view.py (_form_reason, _form_tile, EMPTY_NOTE), src/trevor/signals.py (build), src/trevor/wearables/watch_summary.py (_health), data/i18n/fr.json (textReadiness*)
Lignes de base et écarts à ta normale
Trévor compare chaque mesure de ta montre ou de ta bague à ce qui est habituel pour toi, et non à une norme générale. Pour cela il calcule une médiane et un écart absolu médian (MAD) sur tes jours précédents, puis transforme l'écart du jour en z-score robuste et en tendance sur 3 jours. Ces écarts sont la matière première du risque de crise, du risque de baisse de forme et de la forme du jour.
Où tu le vois : Jamais affiché sous forme de chiffre brut. Les écarts apparaissent dans les phrases des écrans « Mon risque de crise » et « Ma forme du jour » (par exemple « Ton sommeil est plus court que d'habitude »), dans la liste des signaux qui pèsent le plus, et dans la phrase qui explique pourquoi une forme du jour manque.
Ce que l’algorithme lit
- Fréquence cardiaque au repos, variabilité cardiaque (HRV), SpO2, minutes de sommeil, pas, écart de température cutanée, score de stress, fréquence respiratoire, bruit ambiant, fréquence cardiaque la plus basse de la nuit (montre ou bague, import de fichier)
- Douleur déclarée (0 à 10) et jours de crise (déclaration (suivi du jour, conversation, journal des crises))
- Oxygène de la nuit et sommeil détaillé (efficacité, réveils, sommeil profond, heure de coucher) (montre, bague, oxymètre ou saisie « Ma nuit »)
Le calcul, étape par étape
Les valeurs utilisées
| Nom dans le code | Valeur | Rôle | Où |
|---|---|---|---|
BASELINE_DAYS | 30 | Nombre d'enregistrements précédents qui forment la ligne de base | src/trevor/wearables/crisis.py:121 |
MIN_BASELINE | 7 | Valeurs minimales d'une mesure pour calculer sa médiane, et enregistrements minimaux avant un jour pour qu'il ait des écarts | src/trevor/wearables/crisis.py:122 |
clip (borne du z-score) | 4,0 | Un écart ne compte jamais plus de 4 dispersions, dans un sens comme dans l'autre | src/trevor/wearables/crisis.py:179 |
facteur MAD | 1,4826 | Convertit le MAD en équivalent d'écart-type pour une distribution normale | src/trevor/wearables/crisis.py:176 |
MIN_SD FC repos | 2,0 bpm | Plancher de dispersion : évite qu'une ligne de base très stable transforme un petit écart en alerte | src/trevor/wearables/crisis.py:164 |
MIN_SD HRV | 4,0 ms | Plancher de dispersion de la variabilité cardiaque | src/trevor/wearables/crisis.py:164 |
MIN_SD SpO2 | 0,7 point | Plancher de dispersion de la saturation en oxygène | src/trevor/wearables/crisis.py:164 |
MIN_SD sommeil | 30 min | Plancher de dispersion de la durée de sommeil | src/trevor/wearables/crisis.py:164 |
MIN_SD pas | 1000 pas | Plancher de dispersion de l'activité | src/trevor/wearables/crisis.py:164 |
MIN_SD température, stress, respiration, bruit | 0,15 °C ; 5 ; 1,0 /min ; 5 dB | Planchers de dispersion des autres mesures du jour | src/trevor/wearables/crisis.py:164-165 |
MIN_SD FC la plus basse de la nuit | 2,0 | Plancher de dispersion de la fréquence cardiaque minimale nocturne | src/trevor/wearables/crisis_signaux.py:19 |
MIN_SD nuits | oxygène moyen 0,7 ; creux 1,5 ; temps sous 90 % 1,0 ; chutes/h 2,0 ; efficacité 3,0 ; réveils 10 ; profond 12 ; coucher 30 min | Planchers de dispersion des mesures de nuit | src/trevor/wearables/crisis.py:166-167 |
dispersion par défaut | 1,0 | Plancher utilisé pour une mesure absente de la table MIN_SD | src/trevor/wearables/crisis.py:176 |
température sans ligne de base | z = écart / 0,25, plafonné à 4 | Quand la température n'a pas assez de nuits de référence, l'écart fourni par l'appareil est divisé par 0,25 °C | src/trevor/wearables/crisis.py:382 |
douleur récente | max des 3 derniers jours / 10 | Plus forte douleur déclarée sur aujourd'hui et les 2 jours précédents | src/trevor/wearables/crisis.py:397-398 |
crisis_last7 | 1 si une crise dans les 7 enregistrements précédents | Repère binaire de crise récente | src/trevor/wearables/crisis.py:399 |
pain_days7 | 0,5 par jour de douleur de 3/10 ou plus, maximum 2 | Jours de douleur dans les 7 derniers enregistrements, aujourd'hui compris | src/trevor/wearables/crisis.py:306-308 |
low_spo2 | SpO2 du jour < 92 | Repère binaire de saturation basse | src/trevor/wearables/crisis.py:401 |
fever_flag | écart de température ≥ 0,5 | Repère binaire de signe de fièvre | src/trevor/wearables/crisis.py:402 |
DROP_Z | −1,5 | Une nuit est « en baisse » quand son oxygène moyen est au moins 1,5 dispersion sous ta normale | src/trevor/wearables/crisis.py:189 |
ABS_LOW_NIGHT | 92 | Moyenne nocturne sous laquelle une nuit compte comme basse, sans ligne de base nécessaire | src/trevor/wearables/crisis.py:190 |
NADIR_ABS_REF / NADIR_ABS_STEP | 90 ; 4 | Sous 90 % de creux, la sévérité monte d'un point par tranche de 4 points (86 % : 1, 82 % : 2, 78 % : 3), plafond 3, moitié si la mesure est de qualité faible | src/trevor/wearables/crisis.py:186-187, 311-318 |
tolérance de coucher | 0,5 h, plafond 4 | L'irrégularité de l'heure de coucher compte à partir d'une demi-heure d'écart à ton heure habituelle | src/trevor/wearables/crisis.py:234 |
Ce qui est affiché
Un dictionnaire de caractéristiques par jour : z-scores bornés de −4 à +4 (FC repos, HRV, SpO2, sommeil, pas, température, stress, respiration, bruit, FC minimale de nuit), tendances sur 3 jours (FC repos, HRV, sommeil, respiration), douleur récente, jours de douleur, crise récente, SpO2 basse, signe de fièvre et caractéristiques de nuit. Un z positif veut dire « au-dessus de ta normale ». Le sens favorable ou défavorable n'est donné que plus loin, par les poids des modèles.
Un exemple chiffré
Huit jours de FC au repos : 60, 61, 59, 62, 60, 58, 61, 60. La médiane est 60. Les distances à 60 sont 0, 1, 1, 2, 0, 2, 1, 0, dont la médiane (le MAD) est 1. 1,4826 × 1 = 1,48 est sous le plancher de 2,0 bpm, donc la dispersion vaut 2,0. Un jour à 66 bpm donne z = (66 − 60) / 2,0 = +3,0. Un jour à 68 bpm donne +4,0, valeur maximale, et un jour à 75 bpm donne aussi +4,0. Résultat vérifié en exécutant _robust et _z du dépôt (médiane 60,0, dispersion 2,0, z de 68 bpm = 4,0).
Ce que cet algorithme ne sait pas faire
- La fenêtre de 30 compte des enregistrements et non des jours du calendrier : un trou de plusieurs semaines sans mesure fait remonter la ligne de base plus loin dans le passé.
- Il suffit de 7 valeurs pour fixer une normale. Avec si peu de jours, la médiane et le MAD sont peu stables, et le plancher de dispersion décide souvent seul.
- Un jour très atypique mais déjà passé (une crise, une maladie) entre dans la ligne de base des jours suivants. La médiane et le MAD y résistent mieux qu'une moyenne, mais une longue période anormale devient peu à peu ta nouvelle normale.
- Une mesure absente vaut un écart de 0, c'est-à-dire neutre. Un modèle qui lit peu de mesures ne voit donc pas de différence entre « tout va bien » et « rien n'a été mesuré ».
- Le z est borné à 4 : un écart énorme et un écart très grand pèsent pareil.
- Les mesures de poignet ou de doigt sont bruitées (mouvement, port irrégulier). Trévor ne filtre pas ces valeurs avant la médiane, hors le rejet des nuits de qualité faible pour les mesures de nuit.
Code : src/trevor/wearables/crisis.py (_robust, _z, MIN_SD, build_features, night_features, nadir_abs, pain_days), src/trevor/wearables/crisis_signaux.py (features), src/trevor/wearables/readiness.py (même méthode pour 3 mesures)
Que faire maintenant ?
Quand ton risque de crise monte, que ta forme est moyenne ou mauvaise, ou qu'une douleur commence, Trévor te propose de 3 à 5 gestes concrets. Il commence toujours par la consigne de sécurité s'il voit un signe d'urgence, puis il met en avant les gestes qui t'ont déjà aidé, les repères généraux, et enfin ce que d'autres personnes observent si leur nombre est suffisant. Ce sont des gestes d'auto-soin généraux, jamais une dose de médicament.
Où tu le vois : Écran « Que faire maintenant ? » (commande /quefaire ou /action, bouton proposé dès 40 % de risque), cartes de l'application compagnon (GET /api/phone/reco), et les deux premiers gestes dans le second message de l'alerte du matin. Chaque geste a une raison (« Pourquoi »), parfois une preuve (« Chez toi » ou « Parmi des personnes qui utilisent Trévor »), et un bouton « Je l'ai fait ».
Ce que l’algorithme lit
- Score et niveau du risque de crise du jour (calcul Trévor (montre, déclaration, dossier))
- Forme du jour de l'appareil ou calculée, humeur du jour (bien 8, moyen 5, pas bien 3), « Ça ne va pas » au suivi du jour : la pire de ces formes compte (montre, déclaration)
- Douleur du jour (la plus forte entre les observations et le suivi du jour) (déclaration)
- Signes d'urgence : fièvre déclarée ou température mesurée à 38,3 °C ou plus, essoufflement, douleur dans la poitrine, confusion, déficit neurologique (choisis dans l'application), douleur qui monte malgré le traitement (déclaration, calcul)
- Saturation mesurée (92 % ou moins), oxygène de nuit plus bas que ta normale plusieurs nuits de suite, nuit plus fragmentée (montre, bague, oxymètre)
- Sensation de froid ou frissons au suivi du jour (déclaration)
- Plan de soins : oxygène prévu, antalgique de secours prévu (carte d'urgence, dossier)
- Gestes que tu as notés et leur effet sur ta douleur ou ta forme le lendemain (déclaration (« Je l'ai fait »))
- Effets observés chez les autres personnes qui utilisent Trévor (anonymes, agrégés) (données de la cohorte)
Le calcul, étape par étape
Les valeurs utilisées
| Nom dans le code | Valeur | Rôle | Où |
|---|---|---|---|
RISK_MODERATE | 40 | Score à partir duquel « Que faire maintenant ? » se propose, et niveau « modéré » côté gestes | src/trevor/navigator/reco.py:52 |
LEVEL_OF_SCORE | 70 élevé ; 40 modéré ; sinon faible | Seuils de niveau utilisés ici pour ordonner les gestes. Ils diffèrent de ceux de l'écran du risque (30 et 60) | src/trevor/navigator/reco.py:263-269 |
FEVER_C | 38,3 °C (lu dans data/emergency_thresholds.json) | Température mesurée qui compte comme fièvre à signaler | src/trevor/navigator/reco.py:53 |
SPO2_LOW | 92 | Une saturation mesurée de 92 % ou moins (dans les 2 derniers jours) déclenche la consigne de sécurité. Le code précise : mesure bruitée, seuil à valider cliniquement | src/trevor/navigator/reco.py:54 ; src/trevor/navigator/reco_flow.py:124-136, 232 |
PAIN_ONSET | 3 | Douleur de 3/10 ou plus : « une douleur commence » | src/trevor/navigator/reco.py:55 |
MIN_ACTIONS / MAX_ACTIONS | 3 ; 5 | Nombre de gestes montrés (5 au plus ; le minimum n'est pas appliqué dans le code) | src/trevor/navigator/reco.py:48-49, 162-190 |
MAX_PERSONAL / MAX_PEERS | 2 ; 1 | Gestes « pour toi » et « autres personnes » au plus | src/trevor/navigator/reco.py:50-51 |
RISING_POINTS / RISING_MIN_LEVEL | 2 points ; 4/10 | Douleur qui monte malgré le traitement : une prise d'antalgique de secours dans les 24 h ET une douleur notée d'au moins 4/10 qui dépasse de 2 points ou plus la plus basse des 24 h | src/trevor/navigator/reco_flow.py:28-29, 108-118 |
MIN_USES / MIN_PAIRED / MIN_USUAL | 5 ; 5 ; 10 | Utilisations du geste, lendemains mesurés et autres jours mesurés nécessaires avant de parler d'un effet « pour toi » | src/trevor/navigator/reco.py:57-59 |
NOTABLE_PAIN / NOTABLE_FORM | 1 point ; 8 points | Écart minimal favorable de douleur (0 à 10) ou de forme (0 à 100) du lendemain face à la moyenne des autres jours | src/trevor/navigator/reco.py:60-61 |
PEER_MIN_DIFF | 0,5 point | Écart médian de douleur pour présenter un geste des autres personnes comme associé à moins de douleur | src/trevor/navigator/reco.py:62 |
DEFAULT_MIN_PEOPLE / MIN_PER_GESTURE | 20 ; 5 | Personnes minimales pour montrer ce que d'autres observent, et par geste | src/trevor/navigator/reco_peers.py:27-28 |
FOLLOWUP_DELAY / FOLLOWUP_STALE / ASKED_VALID | 2 h ; 8 h ; 12 h | Délai avant « Ça va mieux ? », durée avant abandon de la question en attente, durée de validité de la réponse | src/trevor/navigator/reco_flow.py:23-25 |
eau | 1,5 à 2 litres par jour | Repère d'hydratation affiché dans le geste « eau » et dans l'alerte (converti dans l'unité préférée de la personne) | src/trevor/navigator/reco.py:17-18 ; src/trevor/units.py:523 |
Ce qui est affiché
Une liste de 5 gestes au plus. Chaque geste porte une source : « securite » (consigne d'urgence ou équipe de soins), « perso » (« Chez toi »), « plan » (« Ton plan » : antalgique de secours, oxygène prévu), « general » (repère général) ou « autres ». Gestes du catalogue : urgence, équipe de soins la nuit, eau, repos au chaud, éviter le froid, éviter l'effort intense, éviter l'alcool, chaleur locale, respiration et relaxation, antalgique du plan (sans dose), antalgique prêt, oxygène du plan, sommeil, repas réguliers, prévenir un proche. L'écran se termine par « Ces gestes ne remplacent pas ton équipe soignante. Si tu te sens moins bien, contacte-la. » Il faut appeler le 144 sans attendre en cas de signe grave.
Un exemple chiffré
Contexte : score 75 (élevé), forme moyenne, douleur 4/10, fièvre déclarée, plan avec antalgique de secours nommé, sans oxygène. La fièvre est un signe d'urgence, donc la première carte est la consigne de sécurité. Il reste 4 places. Comme la douleur est de 3 ou plus, l'ordre est eau, repos au chaud, chaleur locale, antalgique du plan, puis la respiration. Résultat de reco.build : urgence (securite), eau (general), repos au chaud (general), chaleur (general), antalgique du plan (plan). Deuxième cas : score 45 (modéré), forme bonne, aucune douleur, aucun signe : eau, repos au chaud, éviter le froid, sommeil, repas réguliers. Vérifié en exécutant reco.build du dépôt.
Ce que cet algorithme ne sait pas faire
- Le niveau utilisé ici (élevé à partir de 70, modéré de 40 à 69) diffère de celui de l'écran du risque (modéré dès 30, élevé dès 60). Un score de 65 est « élevé » à l'écran du risque, mais « modéré » pour l'ordre des gestes.
- Les signes d'urgence viennent de ce que tu déclares, du suivi du jour, d'une saturation mesurée et d'un calcul de douleur qui monte. Si tu ne déclares rien et qu'aucune mesure n'existe, aucune consigne de sécurité n'apparaît. Un score bas n'exclut jamais une crise.
- Le seuil de saturation de 92 % vient d'une mesure de montre ou de bague, bruitée. Le code précise qu'il est à valider cliniquement. Le geste proposé est toujours de contacter l'équipe de soins, jamais de te donner de l'oxygène.
- Les gestes « pour toi » reposent sur une association entre un geste et la douleur ou la forme du lendemain, sans preuve de cause. Tu fais souvent un geste parce que tu as mal, ce qui brouille le lien. Un effet défavorable n'est jamais montré comme conseil.
- Les gestes des autres personnes exigent au moins 20 personnes. Avec moins, rien n'est montré. Le calcul est mis en cache 6 heures.
- Les textes des gestes sont généraux. Aucune dose de médicament n'est donnée, et l'antalgique de secours n'entre pas dans les effets « pour toi ».
- Le nombre minimal de 3 gestes est annoncé dans le code mais pas imposé : une liste plus courte est possible, par exemple quand peu de gestes généraux restent disponibles.
Code : src/trevor/navigator/reco.py (build, _general_order, emergency_signs, personal_effects, gesture_effect, favourable, level_of, triggered, form_of_mood, worst_form), src/trevor/navigator/reco_flow.py (context_for, compute, present, pain_rising, plan_of, record_done, alert_lines, api_payload), src/trevor/navigator/reco_peers.py (aggregate, peer_actions), src/trevor/conversation/reco_screen.py, src/trevor/conversation/reminders.py (crisis_alerts : deuxième message), data/i18n/fr.json (textReco*)
Risque de baisse de forme
Le risque de baisse de forme est un second score, plus doux et plus fréquent que le risque de crise. Il estime la probabilité d'un fléchissement de ton quotidien (sommeil, énergie, douleur) dans les 48 heures. Il sert à agir tôt, par exemple avec du repos, de l'hydratation et un rythme plus calme, avant une éventuelle crise.
Où tu le vois : Dans le message du bot « Mon risque de crise » (écran Ma montre), juste sous le risque de crise : une ligne « Risque de baisse de forme : X % · niveau » avec une icône verte, orange ou rouge, une phrase d'explication, puis une puce par signal qui pèse. Aucun autre écran, ni l'application, ni la montre, ni le tableau de bord n'affichent ce score (le code ne l'envoie qu'à ce message).
Ce que l’algorithme lit
- Écarts à ta normale de ton sommeil, de ton activité (pas), de ta HRV, de ta FC au repos, de ton stress mesuré, de ta température cutanée et de ta SpO2, plus les tendances sur 3 jours (voir « Lignes de base et écarts ») (montre ou bague)
- Douleur déclarée des 3 derniers jours, jours de douleur de la semaine, crise des 7 derniers jours (déclaration)
- Hémoglobine basse, retard de transfusion, effet de la transfusion qui s'estompe, transfusion récente ou qui tient, hydroxyurée suivie ou irrégulière (dossier, déclaration, import de laboratoire)
- Nuits de suite avec un oxygène bas (montre, oxymètre ou saisie « Ma nuit »)
- Froid, chaleur et particules fines PM2.5 du lieu (météo et qualité de l'air du lieu)
Le calcul, étape par étape
Les valeurs utilisées
| Nom dans le code | Valeur | Rôle | Où |
|---|---|---|---|
FORM_BIAS | −1,4 | Point de départ du calcul : un jour où tous les signaux sont à 0 donne 20 % (1 / (1 + e^1,4) = 0,198), contre environ 10 % pour la crise | src/trevor/wearables/crisis.py:605 |
seuil modéré | score ≥ 30 | Les seuils sont écrits en dur dans form_risk, avec les mêmes valeurs que le risque de crise | src/trevor/wearables/crisis.py:615 |
seuil élevé | score ≥ 60 | Niveau « élevé » | src/trevor/wearables/crisis.py:615 |
seuil d'affichage d'une contribution | 0,15 | Un signal dont |poids × valeur| est sous 0,15 n'est pas expliqué | src/trevor/wearables/crisis.py:616 |
nombre d'explications | 4 | Au plus quatre signaux affichés | src/trevor/wearables/crisis.py:617 |
STALE_SHARE | 0,6 | Part effacée des signaux d'avant la transfusion le jour même (× e^(−jours/6) ensuite) | src/trevor/wearables/crisis.py:529 |
clés atténuées après transfusion | z_spo2, z_spo2_night, low_spo2, night_nadir, night_nadir_abs, night_abs_low, night_time_low, night_odi, night_drop_streak, hb_low, transfusion_overdue, crisis_last7, pain_days7 | Parmi elles, seules z_spo2, hb_low, transfusion_overdue, crisis_last7, pain_days7 et night_abs_low pèsent dans la baisse de forme | src/trevor/wearables/crisis.py:524-528 |
TX_RECENT_DAYS / TX_RECENT_WINDOW | 6 jours ; 21 jours | tx_recent = e^(−jours/6) pendant 21 jours après la transfusion, puis 0 | src/trevor/wearables/crisis.py:270-271, 292-293 |
hold_days | 5 jours par culot, de 14 à 56 jours ; 28 sans nombre de culots | Durée de tenue de la transfusion, qui règle tx_protect et tx_loss | src/trevor/wearables/crisis.py:268-279 |
hb_low | (9,5 − Hb) / 2, entre 0 et 4 | Hémoglobine sous 9,5 g/dL ; 7,5 g/dL donne 1 | src/trevor/wearables/crisis.py:651-660 |
transfusion_overdue | retard (fraction d'intervalle) × 2, entre 0 et 4 | Seulement pour une personne en programme transfusionnel | src/trevor/wearables/crisis.py:658-659 |
cold_ambient | (10 °C − tmin) / 10, entre 0 et 4 | Froid sous 10 °C | src/trevor/wearables/crisis.py:104, 251 |
heat_ambient | (tmax − 28 °C) / 6, entre 0 et 4 | Chaleur au-dessus de 28 °C | src/trevor/wearables/crisis.py:105, 252 |
air_pm25 | (PM2.5 − 15) / 15, entre 0 et 4 | Particules fines au-dessus de la ligne directrice de l'OMS (15 µg/m³ sur 24 h) | src/trevor/wearables/crisis.py:107, 255 |
Les poids
Un poids positif augmente le résultat, un poids négatif le diminue.
| Signal | Poids | Où |
|---|---|---|
| Douleur déclarée récente (plus forte des 3 derniers jours / 10) | +0,8 | src/trevor/wearables/crisis.py:599 |
| Effet de la dernière transfusion qui s'estompe | +0,8 | src/trevor/wearables/crisis.py:602 |
| Sommeil plus court que ta normale (z négatif) | −0,6 | src/trevor/wearables/crisis.py:597 |
| Activité plus faible que ta normale (z négatif) | −0,5 | src/trevor/wearables/crisis.py:597 |
| Hémoglobine basse | +0,5 | src/trevor/wearables/crisis.py:599 |
| Stress mesuré plus élevé que ta normale | +0,45 | src/trevor/wearables/crisis.py:597 |
| HRV plus basse que ta normale (z négatif) | −0,4 | src/trevor/wearables/crisis.py:597 |
| FC au repos plus rapide que ta normale | +0,4 | src/trevor/wearables/crisis.py:597 |
| Crise dans les 7 derniers jours | +0,4 | src/trevor/wearables/crisis.py:599 |
| Jours de douleur dans la semaine (0 à 2) | +0,4 | src/trevor/wearables/crisis.py:600 |
| Sommeil sur 3 jours (z négatif) | −0,35 | src/trevor/wearables/crisis.py:598 |
| Retard de transfusion | +0,35 | src/trevor/wearables/crisis.py:599 |
| SpO2 vs ta normale (z négatif) | −0,3 | src/trevor/wearables/crisis.py:597 |
| Hydroxyurée suivie de façon irrégulière | +0,3 | src/trevor/wearables/crisis.py:602 |
| Nuits de suite avec un oxygène bas (0 à 4) | +0,25 | src/trevor/wearables/crisis.py:600 |
| Température cutanée vs ta normale | +0,2 | src/trevor/wearables/crisis.py:597 |
| HRV sur 3 jours (z négatif) ; FC repos sur 3 jours | −0,2 ; +0,2 | src/trevor/wearables/crisis.py:598 |
| Froid extérieur ; chaleur extérieure ; PM2.5 | +0,2 chacun | src/trevor/wearables/crisis.py:603 |
| Hydroxyurée prise régulièrement | −0,2 | src/trevor/wearables/crisis.py:602 |
| Transfusion récente ; transfusion qui tient encore | −1,5 chacun | src/trevor/wearables/crisis.py:601 |
Ce qui est affiché
Un pourcentage de 0 à 100, un niveau et jusqu'à 4 explications. Niveaux : faible sous 30 % (icône verte), modéré de 30 à 59 % (orange), élevé à partir de 60 % (rouge). Le bot écrit « Risque de baisse de forme : X % · niveau », puis « Un fléchissement possible de ta forme (sommeil, énergie, douleur) dans les 48 h, plus léger qu'une crise », puis une puce par explication, par exemple « Ton sommeil est plus court que d'habitude : cela fait monter ton risque de baisse de forme. ». Le sens des signaux pour la forme se lit dans les poids : un z de sommeil négatif multiplié par −0,6 donne une contribution positive.
Un exemple chiffré
Sans aucun écart, tous les signaux valent 0 et le score est de 20 % (niveau faible). Prenons une journée où le sommeil est à −2 dispersions, l'activité à −1,5, la HRV à −1, la FC au repos à +1, le stress à +1 et la douleur récente à 0,5 (5/10). Le logit vaut −1,4 + 1,2 (sommeil) + 0,75 (pas) + 0,4 (HRV) + 0,4 (FC) + 0,45 (stress) + 0,4 (douleur) = 2,2. La probabilité est 1 / (1 + e^−2,2) = 0,90, soit 90 % (niveau élevé). Les quatre explications retenues sont le sommeil (1,2), les pas (0,75), le stress (0,45), puis la HRV (0,4), qui égale la FC et la douleur à 0,4 et passe devant dans l'ordre du tableau des poids. Résultat obtenu en exécutant form_risk du dépôt (score 90, niveau élevé, contributions z_sleep 1,2 ; z_steps 0,75 ; z_stress 0,45 ; z_hrv 0,4).
Ce que cet algorithme ne sait pas faire
- Les poids sont fixes. Contrairement au risque de crise, le risque de baisse de forme n'est jamais entraîné sur tes données ni sur celles de la cohorte, et aucune AUROC n'existe pour lui. Rien ne dit que ce score prédit vraiment une baisse de forme.
- Aucune définition d'une « baisse de forme » n'est écrite dans le code : il n'y a ni étiquette, ni cible mesurée. Le « 48 h » du texte est une intention, non une quantité vérifiée.
- Un jour calme part à 20 % : le niveau « faible » est donc atteint de justesse avec un seul signal moyen. Un signal à lui seul de poids 0,8 avec une valeur de 1 fait passer à 35 %, niveau « modéré ».
- Le score n'a pas de plafond à 99 %, ni de plafond après transfusion. Une journée très chargée peut dépasser 99 %.
- Les symptômes que tu déclares (fièvre, difficultés à respirer, fatigue, mauvaise nuit, mal-être) n'entrent pas dans ce score, alors qu'ils entrent dans le risque de crise. Ta fatigue déclarée ne fait donc pas monter le risque de baisse de forme.
- Quand l'oxygène de la nuit est mesuré, il remplace la SpO2 du jour dans le calcul des écarts, mais la baisse de forme ne lit pas l'oxygène moyen de la nuit. La SpO2 pèse alors 0 dans ce score, et seules les nuits de suite avec un oxygène bas (0,25) restent.
- Une moyenne d'oxygène meilleure que d'habitude peut faire baisser le score même si la nuit a eu un creux, car la règle de non-rassurance du risque de crise n'est pas appliquée ici.
- Sans montre ni bague, les écarts valent 0. Le score ne repose alors que sur la douleur, le dossier et l'environnement, et peut sembler rassurant par défaut.
- Le score n'est pas calculé sur le téléphone : l'application compagnon ne reçoit pas ses poids. Il n'existe que dans le message du bot.
Code : src/trevor/wearables/crisis.py (FORM_WEIGHTS, FORM_BIAS, form_risk, after_transfusion, explain, reason_label, EXPLAIN_UP, EXPLAIN_DOWN, FEATURE_LABELS), src/trevor/wearables/service.py (crisis_risk, lignes 793-796 ; crisis_text, lignes 1019-1025), data/i18n/fr.json (textCrisisFormHead)
Signaux de la montre et sources de température et de cœur
Trévor lit trois familles de signaux issus de la montre, de la bague ou de fichiers. Les signaux factuels sont des repères simples (SpO2 sous 92 %, FC au repos 10 bpm plus haute, sommeil court, etc.) affichés avec leur date. Trois caractéristiques de température et de cœur entrent dans le risque de crise avec des poids modestes, et le module des signaux compare température et cœur à ta ligne de base pour ses propres écrans.
Où tu le vois : Les signaux factuels apparaissent dans le résumé de la montre (« Mes 7 derniers jours ») et dans le tableau de bord des soignants sous la forme « Objet connecté : SpO2 nocturne 91 % ». Les trois caractéristiques du risque se lisent dans les phrases du risque de crise, avec en dessous la ligne « d'où viennent la température et le cœur » (source, type, méthode, qualité). L'écran Signaux montre la température et le cœur face à ta ligne de base.
Ce que l’algorithme lit
- SpO2 moyenne de nuit, fréquence cardiaque au repos, HRV, sommeil, écart de température cutanée, fréquence respiratoire (montre ou bague, import de fichier)
- Fréquence cardiaque la plus basse de la nuit et écart de température de peau fournis par d'autres sources (fichiers importés, saisie, hôpital)
- Les 30 jours précédents de chaque mesure (montre ou bague)
Le calcul, étape par étape
Les valeurs utilisées
| Nom dans le code | Valeur | Rôle | Où |
|---|---|---|---|
SpO2 nocturne basse | < 92 | Seuil courant d'alerte des oxymètres grand public, selon le code | src/trevor/wearables/service.py:486 |
FC repos haute | ≥ médiane des 30 jours précédents + 10 bpm (au moins 5 valeurs) | Signal de cœur au repos plus rapide | src/trevor/wearables/service.py:488 |
HRV basse | ≤ 0,75 × médiane (au moins 5 valeurs) | Baisse de 25 % ou plus | src/trevor/wearables/service.py:490 |
température cutanée haute | écart ≥ +0,5 °C | Signal de fièvre possible (bague Oura, selon le code) | src/trevor/wearables/service.py:492 |
fréquence respiratoire haute | ≥ 20 par minute | Seuil usuel de polypnée chez l'adulte, selon le code | src/trevor/wearables/service.py:494 |
sommeil court | < 5 h trois nuits de suite ; une nuit < 2 h est ignorée | Une nuit de moins de 2 h est probablement un appareil non porté | src/trevor/wearables/service.py:497-506 |
ELEVATED_TEMP_DELTA | 0,3 °C | Seuil d'écart de la peau pour compter une nuit élevée dans temp_nights (même valeur que dans data/signaux_sources.json) | src/trevor/wearables/crisis_signaux.py:20 |
MAX_NIGHTS | 4 | Plafond du nombre de nuits d'affilée comptées | src/trevor/wearables/crisis_signaux.py:21 |
temp_hr_coupling | min(3 ; max(0 ; z_temp)) × min(3 ; max(0 ; z FC repos, z FC nuit)) / 3 | Calculé si au moins une nuit de peau élevée ou un z de température positif ; sinon 0 | src/trevor/wearables/crisis_signaux.py:62-63 |
NEW_PRIOR_WEIGHTS | z_night_hr 0,3 ; temp_nights 0,25 ; temp_hr_coupling 0,3 | Poids a priori dans le risque de crise (multipliés ensuite par 1,6) | src/trevor/wearables/crisis_signaux.py:18 |
baseline du module signaux | fenêtre 30 jours ; 3 nuits minimum ; +0,3 °C ; +8 bpm ; MAD plancher 3 bpm ; tachycardie de repos 100 bpm ; saut de repos 20 bpm | Valeurs du module Signaux pour ses propres écrans. Elles ne servent pas au score de risque | data/signaux_sources.json (baseline) ; src/trevor/signaux/baseline.py:15-24 |
fièvre mesurée | seuil de la table d'urgence (38,3 °C), thermomètre seulement | La température de peau d'un capteur ne déclenche jamais l'urgence seule | src/trevor/signaux/temperature.py:1-9, 32-34 |
Les poids
Un poids positif augmente le résultat, un poids négatif le diminue.
| Signal | Poids | Où |
|---|---|---|
| FC la plus basse de la nuit vs ta normale | +0,3 | src/trevor/wearables/crisis_signaux.py:18 |
| Température de peau haute plusieurs nuits | +0,25 | src/trevor/wearables/crisis_signaux.py:18 |
| Température et FC qui montent ensemble | +0,3 | src/trevor/wearables/crisis_signaux.py:18 |
| Température cutanée vs ta normale (risque de crise) | +0,5 | src/trevor/wearables/crisis.py:81 |
| Signe de fièvre (écart de peau ≥ 0,5 °C) | +0,9 | src/trevor/wearables/crisis.py:83 |
| Respiration vs ta normale | +0,4 | src/trevor/wearables/crisis.py:85 |
Ce qui est affiché
Pour les signaux factuels : une liste de lignes datées, par exemple « SpO2 nocturne 91 % », « FC repos 72 bpm (référence 60) », « HRV 38 ms (référence 52, baisse de 25 % ou plus) », « Température cutanée +0,6 °C », « Fréquence respiratoire 21 /min », « Moins de 5 h de sommeil 3 nuits de suite », du plus récent au plus ancien. Pour le risque de crise : trois nombres (z de la FC la plus basse de la nuit, nuits d'affilée de 0 à 4, couplage de 0 à 3) qui s'ajoutent au calcul, et la liste des sources (jusqu'à 6). Pour l'écran Signaux : les écarts à ta normale de la température et du cœur. Les ECG n'entrent pas dans le score : ils déclenchent seulement des messages d'orientation.
Un exemple chiffré
FC au repos : médiane des 30 jours précédents à 60 bpm. Un jour à 70 bpm donne le signal « FC repos 70 bpm (référence 60) », car 70 ≥ 60 + 10. Un jour à 69 bpm n'en donne pas. Pour le couplage, avec un z de température de +2 et un z de FC de nuit de +3 : min(3 ; 2) × min(3 ; 3) / 3 = 2. Contribution au risque de crise avant amplification : 0,3 × 2 = 0,6 ; après le gain de 1,6 : 0,96 sur l'échelle du modèle. Calcul fait à la main à partir des formules du code, non exécuté.
Ce que cet algorithme ne sait pas faire
- Les signaux factuels sont des seuils fixes sur des valeurs de poignet ou de doigt, mesurés par un capteur grand public. Les valeurs de SpO2 de nuit et de température de peau sont sensibles aux mouvements et au port de l'appareil.
- Le signal de sommeil court ignore une nuit sous 2 h, donc une vraie très courte nuit peut ne pas compter.
- Il y a deux lignes de base différentes pour le cœur : celle du risque de crise (médiane et MAD sur 30 enregistrements, minimum 7) et celle des signaux factuels et du module Signaux (30 jours, minimum 5 ou 3 nuits, seuils en bpm). Les seuils (+10 bpm, +8 bpm) ne sont pas les mêmes.
- La température de peau n'est pas la température du corps : un capteur ne déclenche jamais l'urgence, et le couplage entre température et cœur invite à prendre la température, sans dire que tu as de la fièvre.
- L'écart de température fourni par l'appareil est déjà un écart à sa propre base. Quand la ligne de base de Trévor manque, il est simplement divisé par 0,25 °C.
- Les autres sources ne complètent que les jours que la montre a déjà mesurés : une FC de nuit ou un écart de peau importés pour un jour sans montre sont ignorés.
- Les signaux factuels et la liste des sources disparaissent si l'interprétation est désactivée.
Code : src/trevor/wearables/service.py (signals, summary, day_records, crisis_risk), src/trevor/wearables/crisis_signaux.py (night_streak, features, NEW_PRIOR_WEIGHTS), src/trevor/signaux/service.py (crisis_extras, crisis_sources, crisis_sources_text, build), src/trevor/signaux/baseline.py (against_baseline, consecutive_elevated, robust), src/trevor/signaux/temperature.py (analyse, is_fever), src/trevor/signaux/heart.py (analyse), src/trevor/signals.py (vue « Mesures et estimations »), data/signaux_sources.json (baseline)
Tendance à la vasoconstriction (expérimental)
Cet indice indicatif repère une peau qui se refroidit plus que d'habitude pendant la nuit, signe possible de vasoconstriction. Il compare ta température de peau, ton cœur au repos, ta HRV, ton oxygène et ta respiration à tes 14 jours précédents. Il reste silencieux quand la mesure est fragile, et il n'est calculé que si tu as donné ton accord.
Où tu le vois : Au poignet (écran Ma santé de la montre), sous forme de codes seulement (état, niveau, noms des facteurs, qualité), et comme un facteur « Peau qui se refroidit (expérimental) » dans les explications du risque de crise quand il pèse. Aucune valeur de santé n'est affichée par cet indice.
Ce que l’algorithme lit
- Température cutanée de nuit (valeur de peau, ou écart donné par l'appareil) (montre ou bague)
- Fréquence cardiaque au repos, HRV, SpO2, fréquence respiratoire (montre ou bague)
- Qualité de la mesure des 14 derniers jours (calcul Trévor)
- Ton accord « Mon contexte » pour la vasoconstriction, et l'indicateur activé côté Trévor (réglage)
Le calcul, étape par étape
Les valeurs utilisées
| Nom dans le code | Valeur | Rôle | Où |
|---|---|---|---|
BASE_DAYS | 14 | Jours précédents qui forment la base de comparaison (médiane) | src/trevor/wearables/vasoconstriction.py:33 |
MIN_BASE | 5 | Valeurs minimales dans la base, pour chaque mesure | src/trevor/wearables/vasoconstriction.py:34 |
TEMP_DROP_C | 0,3 °C | Baisse de la peau face à la médiane (en plus d'au moins 1 dispersion) | src/trevor/wearables/vasoconstriction.py:35 |
TEMP_MIN_SD | 0,15 °C | Plancher de dispersion de la température | src/trevor/wearables/vasoconstriction.py:36 |
HR_RISE_BPM / HR_MIN_SD | 5 bpm ; 2 bpm | Hausse du cœur au repos et plancher de dispersion | src/trevor/wearables/vasoconstriction.py:37-38 |
HRV_RATIO | 0,75 | HRV au plus 75 % de la médiane (25 % sous elle) | src/trevor/wearables/vasoconstriction.py:39 |
SPO2_DROP | 1,5 point | Baisse de l'oxygène sous la base (plancher de dispersion 0,7) | src/trevor/wearables/vasoconstriction.py:40, 113 |
RESP_RISE | 2 par minute | Hausse de la fréquence respiratoire (plancher de dispersion 1,0) | src/trevor/wearables/vasoconstriction.py:41, 115 |
Z_MIN | 1,0 | Chaque écart doit aussi atteindre une dispersion robuste | src/trevor/wearables/vasoconstriction.py:42 |
WINDOW_DAYS / GOOD_DAYS / MEDIUM_DAYS | 14 ; 10 ; 6 | Jours exploitables pour juger la qualité de la mesure | src/trevor/wearables/mesure.py:16-20 |
MIN_NIGHT_COVERAGE_MIN | 120 | Un sommeil connu de moins de 2 h rend le jour inexploitable | src/trevor/wearables/mesure.py:17, 30 |
poids dans le risque de crise | +0,2 (× 1,6 = +0,32 par niveau) | Poids modeste : le code parle d'association décrite (Mohan 1998, Veluswamy 2020), pas d'une prédiction validée | src/trevor/wearables/crisis.py:92 |
consentement | ctx_vasoconstriction_enabled et accord « Mon contexte » | Sans l'un des deux, le résultat est vide (None) et le facteur vaut 0 | src/trevor/wearables/vasoconstriction.py:134-141 |
Les poids
Un poids positif augmente le résultat, un poids négatif le diminue.
| Signal | Poids | Où |
|---|---|---|
| Peau qui se refroidit (niveau 0, 1 ou 2) | +0,2 | src/trevor/wearables/crisis.py:92 |
Ce qui est affiché
Un résultat avec un état (silence, calme, légère ou nette), un niveau de 0 à 2, les noms des facteurs présents (temp, hr, hrv, spo2, resp), la qualité de la mesure (bonne, moyenne, insuffisante) et le nombre de jours exploitables. Seules les étiquettes sortent, jamais une valeur de santé ni une série, et rien n'est journalisé. Pour le risque de crise, seul le niveau compte : 0 pour silence et calme, 1 pour légère, 2 pour nette. Dans l'application et à la montre, les codes sont silent, calm, slight et clear.
Un exemple chiffré
Quatorze jours de référence : peau autour de 34,0 °C, cœur au repos 58 à 59 bpm, HRV 50 à 52, SpO2 96, respiration 14, sommeil 420 min. Aujourd'hui : peau 33,5 °C et cœur 66 bpm. La peau a baissé de 0,5 °C (au moins 0,3) et de plus d'une dispersion, et le cœur monte de plus de 5 bpm : deux facteurs (temp, hr) avec une qualité bonne, donc état « nette », niveau 2, soit +0,64 sur l'échelle du modèle (2 × 0,32). Si le cœur reste à 59 bpm, seul « temp » est présent : état « légère », niveau 1. Résultat vérifié en exécutant compute() du dépôt (nette, niveau 2, facteurs temp et hr ; légère, niveau 1, facteur temp).
Ce que cet algorithme ne sait pas faire
- Le signal central est la température de la peau mesurée au poignet ou au doigt, qui varie avec la température de la pièce, la literie, le port de l'appareil et le mouvement. Une baisse de 0,3 °C peut venir d'une chambre plus fraîche.
- L'indice de perfusion (PPG) et la température distale de jour ne sont pas collectés par Trévor : ils ne comptent pas, et le texte ne les promet pas.
- Sans refroidissement de la peau, les autres signes sont mis de côté : un cœur plus rapide ou une HRV basse seuls ne disent rien ici sur la vasoconstriction.
- La base ne compte que 14 jours et seulement 5 valeurs, donc elle est fragile. La qualité « insuffisante » rend l'indice silencieux, mais « moyenne » suffit à un état « légère ».
- L'état « nette » exige une qualité « bonne » et au moins deux facteurs. Les facteurs ne sont pas pondérés entre eux.
- Au moment de l'entraînement du modèle, l'indice vaut toujours 0 : son poids n'a jamais été appris sur tes données.
- Une température de peau qui se refroidit n'est pas un diagnostic. Le texte de l'indice reste une aide à la prudence (froid, manque d'eau, stress, infection).
Code : src/trevor/wearables/vasoconstriction.py (compute, level_feature, for_user), src/trevor/wearables/mesure.py (assess, usable), src/trevor/wearables/service.py (_vaso_feature), src/trevor/wearables/crisis.py (PRIOR_WEIGHTS vasoconstriction), src/trevor/wearables/watch_summary.py (_vasoconstriction)
