Coach DrépanoCoach DrépanoCrisis, form and advice
Algorithm transparency

Crisis, form and advice

The crisis risk, the form drop risk, today's form, alerts and “What should I do now?”.

Each algorithm is described with what it reads, its steps, the values it uses (with their place in the code), what it shows and its limits.

Crisis risk alerts and notification limitLearning the personal model and the cohort modelDaily readiness (recovery)Baselines and deviations from your normalWhat should I do now?Risk of a dip in formWatch signals and temperature and heart sourcesVasoconstriction trend (experimental)

← All algorithms

Crisis risk alerts and notification limit

If you turned it on, Trévor writes to you every morning around 8 a.m. when your crisis risk reaches 70% on fresh measurements. An optional soft alert exists from 40%. It never sends more than one alert message per day, and it holds the message during quiet hours.

Where you see it: A bot message received in the morning (a neutral message with an « Open » button on the lock screen, then the content), followed by a second message « Two tips for now ». The buttons « Turn on the morning alert (if risk ≥ 70) » and « Warn me from moderate risk (40%) » are in the « My crisis risk » screen. Caregivers also see an alert line « Crisis risk calculated X% » in the dashboard when the score is 70 or more.

What the algorithm reads

The calculation, step by step

Scheduler pass at 8 a.m.The check starts when the local time of the time-zone group is 8People with watch data are examinedAlert on, feature active andnot already alerted today?yesnoThe risk is computedNothing is sentDoes the risk exist and areits measurements fresh?yesnoFresh: the last measurementis 2 days old or less, or thescore rests only on therecordToday's score is stillstored, but no message issentDoes the score reach 70?yesno« High » alert: drink, restin the warmth, call your teamor 144 if pain risesTest the score of 40Score from 40 to 69 and softalert on?yesnoSoft alert: « Your crisisrisk is rising a little »,nothing alarmingNo messageMark the dayToday's date is saved as the alerted day before sendingA single alert message per day, soft or highCompose the two messagesMessage 1: score, level, signals separated by semicolons,gestures, emergency instructionsMessage 2: the first 2 gestures of « What should I do now? »,safety first if there is an emergency signNote: experimental feature, not medical adviceIs it night where you are?yesnoThe message is held and sentat the end of quiet hours;dropped if the local day haschangedIt is sent right away, behinda neutral message with « Open»
Flowchart: Crisis risk alerts and notification limit

The values used

Name in the codeValueRoleWhere
CRISIS_ALERT_THRESHOLD70Score from which the « high » alert is sentsrc/trevor/wearables/service.py:511
CRISIS_SOFT_THRESHOLD40Score from which the soft alert is sent, if you turned it onsrc/trevor/wearables/service.py:512
send time8 am local timeThe scheduler launches the risk alerts when the local time of the time-zone group is 8src/trevor/conversation/reminders.py:808-810
last_alert_dateonce per dayIf the last alert date equals the person's current date, no new sendsrc/trevor/wearables/service.py:1257, 1272
freshnesslatest dated measurement at most 2 days oldWith no fresh measurement, no message. Scores computed without a watch (record only) count as freshsrc/trevor/wearables/service.py:736, 763, 1263
alerts off by defaultalerts_enabled false, soft_alert falseThe morning alert must be turned on, then the soft alert separatelysrc/trevor/wearables/service.py:1179-1184 ; src/trevor/navigator/reco_flow.py:52-55
number of messagesat most 2The alert message and the two-tips message (omitted if no gesture is available)src/trevor/conversation/reminders.py:1544-1551
default quiet hours10 pm (included) to 7 am (excluded), local timeWindow during which proactive messages are held. You can change it or turn it offsrc/trevor/localtime.py:182, 214-226
QUIET_DAILYincludes crisis_alertsAn alert held at night is dropped if the local day has changed on wakingsrc/trevor/conversation/reminders.py:99-103
attention budgetcrisis_alerts exemptThe shared limit of proactive messages per day (3 by default, if the feature is on) neither holds nor counts crisis alertssrc/trevor/conversation/attention_budget.py:26, 44-48
study silent modeno alerts during an LRH studyDuring the TREVOR-PILOTE study, the risk is computed and stored but never shown or alertedsrc/trevor/wearables/service.py:1186-1190
dashboard rowscore ≥ 70 and freshFor caregivers: « Crisis risk calculated X% (model …) » with at most 3 explanationssrc/trevor/dashboard/data.py:653-655

What is shown

At most one alert message per day. « High » alert (score of 70 or more): « Crisis risk this morning: X% (high). Signals: … », then gestures (drink about 1.5 to 2 liters, rest in the warmth, avoid intense effort) and the cases where to contact the care team or call 144 (rising pain, fever at the threshold, shortness of breath). Soft alert (40 to 69, if on): « Your crisis risk is rising a little: X% this morning (moderate). Signals: … », with the sentence « Nothing alarming at this stage ». Both messages end with « This feature is experimental and is not medical advice ». The second message gives the first two gestures of « What should I do now? ».

A worked example

Morning score at 72%, measurements dated yesterday, morning alert on, no alert sent today: the score reaches 70, the « high » message is sent, and today's date is saved. At 8:30, no second message is sent even if the score rises to 85. Another case: 45% with the soft alert off, no message. With the soft alert on, the soft message is sent. Another case: 75% but measurements 4 days old, nothing is sent. No numeric result was run: the cases come from the conditions in due_alerts.

What this algorithm cannot do

Validation. The thresholds of 70 and 40 were set by the designer: the code presents them as alert thresholds, and nothing in the repository links them to observed crises, sensitivity or false-alarm rate. The choices of one message per day, sending at 8 a.m., quiet hours from 10 p.m. to 7 a.m. and the soft alert off by default are product design choices (limiting alert fatigue), not the result of a study. The score itself inherits the limits of the crisis risk. This is not a validated medical device.

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)

Learning the personal model and the cohort model

Each night, Trévor can train a logistic regression model on your own days, then a model shared by everyone being followed. The starting weights come from a clinical prior, and training moves them away only if your data justify it. At calculation time, the models that have proven themselves are blended with the prior.

Where you see it: In the « My crisis risk » screen, the « Model reliability » block says whether the calculation starts from clinical markers, the cohort or your own data (number of days, number of crisis episodes, AUROC in temporal validation).

What the algorithm reads

The calculation, step by step

Build the training daysThe last 180 days, one vector of gaps per dayTreatments are replayed day by dayLabel each dayLabel = 1 if a crisis occurs that day or on either of the next 2daysOtherwise 0Enough data for a personalmodel?yesnoAt least 60 days and 4 dayslabeled crisis: trainNo personal model: the priorstays in placeRegularized logistic regressionWeights start from the clinical prior400 rounds of gradient descent, step 0.05Each weight is pulled back toward the prior (L2 = 0.5)Crisis days weigh (calm days / crisis days) times moreBlocked temporal cross-validationAt least 30 days and 3 crises4 contiguous blocks: each one serves as test, the other 3 astraining (200 rounds)AUROC computed over all test scores pooledPersonal modelYour days onlySaved with its AUROCCohort modelAll days of everyone beingfollowed, sorted by dateAt least 60 days and 5 crisesSaved with its AUROCIs the model used atcalculation time?yesnoPersonal: 4 crises or moreand AUROC of 0.65 or more.Cohort: 5 crises or more andAUROC of 0.6 or moreThe model is set aside, withno messageBlending the modelsPrior: weight 1Cohort: weight min(4; crises / 5)Personal: weight min(6; crises / 2)The weighted logits are summed and divided by the sum of theweightsWeights amplified at calculationEach weight is multiplied by 1.6, except the protectionsThe bias does not moveThe rest of the calculation is described on the public crisis riskpage
Flowchart: Learning the personal model and the cohort model

The values used

Name in the codeValueRoleWhere
training days180History re-read for training (training_rows, days=180)src/trevor/wearables/service.py:617
crisis pain thresholdpain_intensity ≥ 7A day is labeled crisis when the reported pain from the daily follow-up reaches 7/10, or when a hospitalization is reportedsrc/trevor/wearables/service.py:553
conversation observationintensity ≥ 7 and a word among crisis, emergency, hospital, cvo, vasoA conversation observation counts as a crisis if its intensity is 7 or more and its text contains one of these wordssrc/trevor/wearables/service.py:513, 568-572
crisis logeach day of a declared episodeEach day between the start and end of a declared episode (until today if ongoing) is a certain crisissrc/trevor/wearables/service.py:573-587
HORIZON_DAYS2A day is positive if a crisis occurs today, tomorrow or the day after (loop over 0 to 2 days inclusive)src/trevor/wearables/crisis.py:123, 413-419
personal model minimum60 days and 4 crisesBelow this, train_user produces nothingsrc/trevor/wearables/service.py:662
cohort model minimum60 days and 5 crisesBelow this, train_cohort produces nothingsrc/trevor/wearables/service.py:677
l20.5Strength of the pull of each weight toward its prior value (the bias is not pulled)src/trevor/wearables/crisis.py:433, 453
epochs / lr400 epochs; step 0.05 (200 epochs in validation)Gradient descent over all days, with no external librarysrc/trevor/wearables/crisis.py:433, 482
pos_weight(days without crisis) / (crisis days)Crisis days count more in the gradient to compensate for their raritysrc/trevor/wearables/crisis.py:442-448
cross-validation4 blocks, at least 30 days and 3 crisesContiguous temporal blocks; without these minimums, the AUROC stays emptysrc/trevor/wearables/crisis.py:470-485
personal model usage thresholdn_events ≥ 4 and AUROC ≥ 0.65With no AUROC computed, the model is not usedsrc/trevor/wearables/crisis.py:508
cohort model usage thresholdn_events ≥ 5 and AUROC ≥ 0.6Cohort AUROC threshold, lower than the personal model'ssrc/trevor/wearables/crisis.py:506
blend weightprior 1; cohort min(4; crises/5); personal min(6; crises/2)The more crises observed, the more the model weighs, up to a capsrc/trevor/wearables/crisis.py:505-509
RISK_GAIN1.6Multiplier applied to all weights in the calculation, except hu_protect, dmt_other, tx_recent and tx_protectsrc/trevor/wearables/crisis.py:494-500
PRIOR_BIAS−2.2About 10% baseline risk when everything is normalsrc/trevor/wearables/crisis.py:120
RISK_MAX0.99The risk never exceeds 99%src/trevor/wearables/crisis.py:493

The weights

A positive weight raises the result, a negative weight lowers it.

SignalWeightWhere
Recent reported pain (prior)+0.9src/trevor/wearables/crisis.py:83
Reported fever (prior)+0.8src/trevor/wearables/crisis.py:90
Reported breathing difficulty (prior)+1.0src/trevor/wearables/crisis.py:90
SpO2 below 92% (prior)+0.8src/trevor/wearables/crisis.py:83
SpO2 vs your normal (prior)−0.6src/trevor/wearables/crisis.py:81
Resting HR vs your normal (prior)+0.55src/trevor/wearables/crisis.py:81
HRV vs your normal (prior)−0.45src/trevor/wearables/crisis.py:81
Crisis in the last 7 days (prior)+0.6src/trevor/wearables/crisis.py:83
Recent transfusion (prior, not amplified)−3.0src/trevor/wearables/crisis.py:78
Transfusion still holding (prior, not amplified)−2.0src/trevor/wearables/crisis.py:78
Fading transfusion effect (prior)+1.4src/trevor/wearables/crisis.py:78
Overdue transfusion (prior)+0.8src/trevor/wearables/crisis.py:93
Absolute night oxygen low (prior)+0.7src/trevor/wearables/crisis.py:97
Low hemoglobin (prior)+0.5src/trevor/wearables/crisis.py:93

What is shown

Two stored models (yours and the cohort's), each with its weights, bias, number of days, number of crises and cross-validated temporal AUROC. At calculation time, the list of retained models and their blend weights. The screen shows where the calculation comes from: « clinical markers » (prior only), « fitted on the cohort » or « fitted on your data », with days, episodes and AUROC.

A worked example

A personal model with 8 crises and an AUROC of 0.70, and a cohort model with 10 crises and an AUROC of 0.62. Both pass their threshold (4 crises and 0.65; 5 crises and 0.6). The blend weights are prior 1.0, cohort min(4; 10/5) = 2.0 and personal min(6; 8/2) = 4.0, so the personal model has a share of 4/7. If the personal AUROC were only 0.60, the personal model would be set aside and only the prior (1.0) and the cohort (2.0) would remain. Verified by running blend() from the repository (result: prior 1.0, cohort 2.0, personal 4.0).

What this algorithm cannot do

Validation. The starting weights (prior) are set from the literature and the designer's judgment, and the code says so: « modest » weights, associations rather than causation, « not validated, to be reviewed with observed crises ». The gain of 1.6 was calibrated on one reported case, and the code states it is « to be reviewed with crises actually observed (cohort, study 1) ». The personal and cohort weights are learned from data, regularized toward the prior. The usage thresholds (AUROC 0.65 for you, 0.6 for the cohort, minimum 4 and 5 crises) are arbitrary: nothing in the repository justifies them with a study. The code header text and the public page speak of a 0.65 threshold for both, but the code applies 0.6 to the cohort. This is not a validated medical device.

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)

Daily readiness (recovery)

The daily readiness is a recovery marker out of 100, in the manner of an Oura score. Trévor shows your device's own score as is when one exists. Otherwise it computes its own from your sleep, heart rate variability (HRV) and resting heart rate, compared with your normal.

Where you see it: « My daily readiness » screen (command /forme, or My health > Readiness, risk, hemoglobin), the « Daily readiness » tile of My health, the Measurements and estimates screen, and the My health screen on the watch. The text gives a percentage, a level (good, medium, low), the source (device score or Trévor estimate), up to 2 factors, advice for the day and a note that it is not medical advice.

What the algorithm reads

The calculation, step by step

Take the last 35 daysDaily series sorted by dateStart from the most recent day and go backwardDoes the device give a scorethat day?yesnoScore rounded and clippedfrom 0 to 100, source «device »Trévor computes its own score(next steps)Is the calculation possible?yesnoInterpretation on, at least 2of the 3 measurements of theday with a baseline, at least7 days beforeTry the previous day, and soonGap of each measurement from your normalRobust z (median and MAD of the previous 30 days), clipped at ±2Sleep and HRV: high = betterResting HR: z is inverted (high = worse)Without a sleep measurement, the reported night replaces it: good+1, average 0, bad −1Weighted averageSleep 0.40; HRV 0.35; resting HR 0.25Only the measurements present count: weights are scaled back to 1score = 50 + average × 15, clipped from 0 to 100Computed score older than 2days?yesnoIt is not shownIt is kept and marked « fresh»Level and factors66 or more: good. 40 to 65: medium. Below 40: lowFactors: the reported night if it weighed, then the 2 measurementswith the largest absolute gap (at least 0.5)DisplayPercentage, level, source, factors, advice for the level,disclaimerOnly a « fresh » readiness (2 days or less) is shown
Flowchart: Daily readiness (recovery)

The values used

Name in the codeValueRoleWhere
WEIGHTSsleep 0.40; HRV 0.35; resting HR 0.25Weights of the three components of the computed score (sum 1)src/trevor/wearables/readiness.py:27
BASELINE_DAYS30Previous days that form the baselinesrc/trevor/wearables/readiness.py:23
FRESH_DAYS2Maximum age of a computed score to be shown; an older device score stays available but not « fresh »src/trevor/wearables/readiness.py:24, 88
series read35 daysLength of the series passed to compute_readinesssrc/trevor/wearables/service.py:1199
z bounds−2 to +2Each gap is limited to ±2 before the averagesrc/trevor/wearables/readiness.py:86
score slope50 + mean × 15An average of +2 gives 80; −2 gives 20src/trevor/wearables/readiness.py:87
good / medium / low level≥ 66 / 40 to 65 / < 40Level thresholdssrc/trevor/wearables/readiness.py:36-37
minimum number of components2With fewer than two measurements that day, the day is skipped (fixed in 2.200.2: before, a day with no measurement gave 50%)src/trevor/wearables/readiness.py:55-57, 83
MIN_BASELINE7Minimum days before the chosen day, and minimum values per measurementsrc/trevor/wearables/crisis.py:122
SELF_SLEEP_Zgood +1.0; average 0.0; bad −1.0Value given to the reported night when the watch did not measure sleepsrc/trevor/navigator/sleep.py:26
factor display threshold|z| ≥ 0.5, at most 2 factorsA weaker factor is not namedsrc/trevor/wearables/readiness.py:96-101

The weights

A positive weight raises the result, a negative weight lowers it.

SignalWeightWhere
Sleep longer than your normal+0.40src/trevor/wearables/readiness.py:27
HRV higher than your normal+0.35src/trevor/wearables/readiness.py:27
Resting HR lower than your normal+0.25src/trevor/wearables/readiness.py:27, 80

What is shown

A score from 0 to 100 (as a percentage), a level (good from 66, medium 40 to 65, low below 40), a source (« device » or « computed »), up to 2 factors as sentences (for example « Shorter night than usual », « Your resting heart is a bit high »), the date and the « fresh » flag. The advice depends on the level: good (great form, keep your good habits), medium (at your pace, hydrate, keep a margin), low (rest, hydrate, spare your efforts, avoid the cold). The text says it is not medical advice and replaces neither how you feel nor your care team. When nothing is shown, a sentence gives the reason: no watch, silent mode, warning to accept, fewer than 7 reference days, measurements too old or too few.

A worked example

Twenty reference days: sleep around 430 min (median 430, spread 30 from the floor), HRV median 51.5 (spread 4), resting HR median 60.5 (spread 2). Today: sleep 360 min, HRV 44, HR 65. The z-scores are −2.33 (clipped to −2), −1.875 and +2.25 for HR, inverted to −2.25 (clipped to −2). The weighted average is (0.40 × −2 + 0.35 × −1.875 + 0.25 × −2) / 1 = −1.96. Score = 50 + (−1.96 × 15) = 20.7, rounded to 21: low level. The two factors shown are sleep (|z| 2.33) then HR (|z| 2.25): « Shorter night than usual » and « Your resting heart is a bit high ». Result verified by running compute_readiness from the repository.

What this algorithm cannot do

Validation. The method is inspired by commercial recovery indicators (the code names Oura and Garmin Body Battery as models), but Trévor's calculation is its own. The weights (0.40; 0.35; 0.25), the slope (15), the level thresholds (66 and 40) and the ±2 bounds are set without a validation study in the repository. The code states that it is a form and recovery indicator, « never medical advice nor a crisis predictor ». This is not a validated medical device.

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*)

Baselines and deviations from your normal

Trévor compares each measurement from your watch or ring with what is usual for you, rather than with a general norm. It computes a median and a median absolute deviation (MAD) over your previous days, then turns today's gap into a robust z-score and a 3-day trend. These gaps are the raw material of the crisis risk, the dip-in-form risk and the daily readiness.

Where you see it: Never shown as a raw number. The gaps appear in the sentences of the « My crisis risk » and « My daily readiness » screens (for example « Your sleep is shorter than usual »), in the list of signals that weigh the most, and in the sentence explaining why a daily readiness is missing.

What the algorithm reads

The calculation, step by step

Gather the daysOne record per day: watch measurements, pain, crisis, reportsDays are sorted by dateAt least 7 records beforethis day?yesnoThe gaps for this day arecomputedThe day is skipped: no gaps,no scoreReference windowThe 30 previous records (not counting the day itself)For each measurement: keep the values that existAt least 7 values for thismeasurement?yesnoMedian and robust spreadcomputedNo baseline for thismeasurement: its gap is 0(neutral)Median and MADmedian = middle value of the reference daysMAD = median of the distances to that medianspread = 1.4826 × MAD, never below a floor specific to themeasurementToday's z-scorez = (today's value − median) / spreadz clipped between −4 and +4Measurement missing today: z = 03-day trendMean of the last 3 days (todayincluded) of resting HR, HRV,sleep and breathingCompared with the samebaselineSimple markersRecent pain = highest pain ofthe last 3 days / 10Crisis in the previous 7 days:0 or 1SpO2 below 92%: 0 or 1Skin temperature +0.5 °C ormore: 0 or 1Night: same calculations, on your nightsMean oxygen, lowest value, time below 90%, drops per hour,efficiency, awakenings, deep sleep, bedtimeCompared with your nights of the previous 30 days, at least 7measured nightsThe absolute oxygen low (below 90%) counts even without a baselineResult: a vector of gapsThis vector goes to the crisis risk and the dip-in-form riskThe daily readiness repeats the same calculation for 3measurements
Flowchart: Baselines and deviations from your normal

The values used

Name in the codeValueRoleWhere
BASELINE_DAYS30Number of previous records that form the baselinesrc/trevor/wearables/crisis.py:121
MIN_BASELINE7Minimum values of a measurement to compute its median, and minimum records before a day for it to have gapssrc/trevor/wearables/crisis.py:122
clip (z-score bound)4.0A gap never counts for more than 4 spreads, in either directionsrc/trevor/wearables/crisis.py:179
MAD factor1.4826Converts the MAD into a standard-deviation equivalent for a normal distributionsrc/trevor/wearables/crisis.py:176
MIN_SD resting HR2.0 bpmSpread floor: prevents a very stable baseline from turning a small gap into an alertsrc/trevor/wearables/crisis.py:164
MIN_SD HRV4.0 msSpread floor for heart rate variabilitysrc/trevor/wearables/crisis.py:164
MIN_SD SpO20.7 pointSpread floor for oxygen saturationsrc/trevor/wearables/crisis.py:164
MIN_SD sleep30 minSpread floor for sleep durationsrc/trevor/wearables/crisis.py:164
MIN_SD steps1000 stepsSpread floor for activitysrc/trevor/wearables/crisis.py:164
MIN_SD temperature, stress, breathing, noise0.15 °C; 5; 1.0 /min; 5 dBSpread floors for the other daily measurementssrc/trevor/wearables/crisis.py:164-165
MIN_SD lowest HR of the night2.0Spread floor for the lowest night heart ratesrc/trevor/wearables/crisis_signaux.py:19
MIN_SD nightsmean oxygen 0.7; trough 1.5; time below 90 % 1.0; drops/h 2.0; efficiency 3.0; awakenings 10; deep sleep 12; bedtime 30 minSpread floors for night measurementssrc/trevor/wearables/crisis.py:166-167
default dispersion1.0Floor used for a measurement missing from the MIN_SD tablesrc/trevor/wearables/crisis.py:176
temperature without baselinez = deviation / 0.25, capped at 4When temperature lacks reference nights, the device's own deviation is divided by 0.25 °Csrc/trevor/wearables/crisis.py:382
recent painmax of the last 3 days / 10Highest reported pain over today and the 2 previous dayssrc/trevor/wearables/crisis.py:397-398
crisis_last71 if a crisis in the previous 7 entriesBinary marker of a recent crisissrc/trevor/wearables/crisis.py:399
pain_days70.5 per pain day of 3/10 or more, maximum 2Pain days in the last 7 records, today includedsrc/trevor/wearables/crisis.py:306-308
low_spo2SpO2 of the day < 92Binary marker of low saturationsrc/trevor/wearables/crisis.py:401
fever_flagtemperature deviation ≥ 0.5Binary marker of a possible fever signsrc/trevor/wearables/crisis.py:402
DROP_Z−1.5A night is « down » when its mean oxygen is at least 1.5 spreads below your normalsrc/trevor/wearables/crisis.py:189
ABS_LOW_NIGHT92Night mean below which a night counts as low, no baseline neededsrc/trevor/wearables/crisis.py:190
NADIR_ABS_REF / NADIR_ABS_STEP90 ; 4Below a 90% low, severity rises by one point per 4 points (86%: 1, 82%: 2, 78%: 3), capped at 3, halved if the measurement quality is lowsrc/trevor/wearables/crisis.py:186-187, 311-318
bedtime tolerance0.5 h, cap 4Bedtime irregularity counts from a half-hour gap from your usual bedtimesrc/trevor/wearables/crisis.py:234

What is shown

A feature dictionary per day: z-scores clipped from −4 to +4 (resting HR, HRV, SpO2, sleep, steps, temperature, stress, breathing, noise, lowest night HR), 3-day trends (resting HR, HRV, sleep, breathing), recent pain, pain days, recent crisis, low SpO2, fever sign and night features. A positive z means « above your normal ». Whether that is favorable or unfavorable is only decided later, by the model weights.

A worked example

Eight days of resting HR: 60, 61, 59, 62, 60, 58, 61, 60. The median is 60. The distances to 60 are 0, 1, 1, 2, 0, 2, 1, 0, whose median (the MAD) is 1. 1.4826 × 1 = 1.48 is below the 2.0 bpm floor, so the spread is 2.0. A day at 66 bpm gives z = (66 − 60) / 2.0 = +3.0. A day at 68 bpm gives +4.0, the maximum, and a day at 75 bpm also gives +4.0. Verified by running _robust and _z from the repository (median 60.0, spread 2.0, z for 68 bpm = 4.0).

What this algorithm cannot do

Validation. The method (median, MAD, robust z-score) is a standard statistical technique, and the code says it is inspired by published approaches on watches and sickle cell disease, including Sanius Health, without citing a specific study. The chosen values are set without a calibration study: 30-day window, 7-value minimum, clip at 4, spread floors (2 bpm, 4 ms, 0.7 SpO2 point, 30 min, 1000 steps) and absolute thresholds (SpO2 92%, +0.5 °C). No document in the repository shows they were compared with observed crises. This is not a validated medical device.

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)

What should I do now?

When your crisis risk rises, your form is medium or low, or pain starts, Trévor suggests 3 to 5 concrete gestures. It always starts with the safety instruction if it sees an emergency sign, then puts forward the gestures that already helped you, the general markers, and finally what other people observe if there are enough of them. These are general self-care gestures, never a medication dose.

Where you see it: « What should I do now? » screen (command /quefaire or /action, button offered from 40% risk), cards in the companion app (GET /api/phone/reco), and the first two gestures in the second message of the morning alert. Each gesture has a reason (« Why »), sometimes evidence (« In your case » or « Among people who use Trévor »), and an « I did it » button.

What the algorithm reads

The calculation, step by step

Gather the contextRisk score and level, form, pain, signs, plan, past gesturesA reading that fails simply leaves the box emptyWhy offer the screen?yesnoRisk of 40 or more, medium orbad form, or pain of 3/10 ormoreThe screen stays available onrequest, with markers of thedayAn emergency sign?yesnoFirst card: contact your careteam now, otherwise theemergency number. It comesbefore anything else,whatever the scoreIf night oxygen is low forseveral nights: card «contact your care team in thenext few days »Order of the general gesturesPain of 3/10 or more: water, rest in the warmth, local heat, planpain medicine, breathingHigh risk: water, rest, avoid cold and effort, plan pain medicineMedium or bad form: rest, sleep, water, mealsModerate risk, otherwise markers of the dayAdjustmentsFragmented night: rest and sleep come firstFeeling of cold: rest, avoid the cold and heat move to the frontOxygen: only if it is in your plan AND the situation looks like acrisis (high risk, pain of 3 or more, low form)« Tell someone close » omitted if all is wellFor you (at most 2)A gesture you logged at least5 times5 measured next days and 10other daysPain lower by at least 1 point(form higher by at least 8)and rank correlation in thesame directionOther people (at most 1)At least 20 people and 5 pergestureMedian pain gap of at least0.5 pointNever oxygenCompose the listSafety, then « for you », then general markers, then other peopleAt most 5 gestures, at least 1 general marker (2 outsideemergencies) stays shown« Feeling better? » loop« I did it » logs the gesture2 hours later, a « Feeling better? » question is sent, outsidequiet hours« Worse » with rising pain leads to the emergency screen
Flowchart: What should I do now?

The values used

Name in the codeValueRoleWhere
RISK_MODERATE40Score from which « What should I do now? » is offered, and « moderate » level for gesturessrc/trevor/navigator/reco.py:52
LEVEL_OF_SCORE70 high; 40 moderate; otherwise lowLevel thresholds used here to order the gestures. They differ from those of the risk screen (30 and 60)src/trevor/navigator/reco.py:263-269
FEVER_C38.3 °C (read from data/emergency_thresholds.json)Measured temperature that counts as a fever to reportsrc/trevor/navigator/reco.py:53
SPO2_LOW92A measured saturation of 92% or less (within the last 2 days) triggers the safety instruction. The code notes: noisy measurement, threshold to be clinically validatedsrc/trevor/navigator/reco.py:54 ; src/trevor/navigator/reco_flow.py:124-136, 232
PAIN_ONSET3Pain of 3/10 or more: « pain is starting »src/trevor/navigator/reco.py:55
MIN_ACTIONS / MAX_ACTIONS3 ; 5Number of gestures shown (5 at most; the minimum is not enforced in the code)src/trevor/navigator/reco.py:48-49, 162-190
MAX_PERSONAL / MAX_PEERS2 ; 1Gestures « for you » and « other people » at mostsrc/trevor/navigator/reco.py:50-51
RISING_POINTS / RISING_MIN_LEVEL2 points; 4/10Pain rising despite treatment: a rescue pain medicine dose in the last 24 h AND a logged pain of at least 4/10 that exceeds the lowest of the last 24 h by 2 points or moresrc/trevor/navigator/reco_flow.py:28-29, 108-118
MIN_USES / MIN_PAIRED / MIN_USUAL5 ; 5 ; 10Gesture uses, measured next days and other measured days needed before mentioning an effect « for you »src/trevor/navigator/reco.py:57-59
NOTABLE_PAIN / NOTABLE_FORM1 point; 8 pointsMinimum favorable gap in next-day pain (0 to 10) or form (0 to 100) compared with the average of other dayssrc/trevor/navigator/reco.py:60-61
PEER_MIN_DIFF0.5 pointMedian pain gap to present another people's gesture as associated with less painsrc/trevor/navigator/reco.py:62
DEFAULT_MIN_PEOPLE / MIN_PER_GESTURE20 ; 5Minimum people to show what others observe, and per gesturesrc/trevor/navigator/reco_peers.py:27-28
FOLLOWUP_DELAY / FOLLOWUP_STALE / ASKED_VALID2 h; 8 h; 12 hDelay before « Feeling better? », time before an unsent question is dropped, validity of the answersrc/trevor/navigator/reco_flow.py:23-25
eau1.5 to 2 liters per dayHydration marker shown in the « water » gesture and in the alert (converted to the person's preferred unit)src/trevor/navigator/reco.py:17-18 ; src/trevor/units.py:523

What is shown

A list of at most 5 gestures. Each gesture carries a source: « securite » (emergency instruction or care team), « perso » (« In your case »), « plan » (« Your plan »: rescue pain medicine, planned oxygen), « general » (general marker) or « autres » (others). Catalog gestures: emergency, care team at night, water, rest in the warmth, avoid the cold, avoid intense effort, avoid alcohol, local heat, breathing and relaxation, plan pain medicine (no dose), rescue medicine ready, plan oxygen, sleep, regular meals, tell someone close. The screen ends with « These gestures do not replace your care team. If you feel worse, contact them. » Call 144 without waiting if there is a serious sign.

A worked example

Context: score 75 (high), medium form, pain 4/10, reported fever, plan with a named rescue pain medicine, no oxygen. Fever is an emergency sign, so the first card is the safety instruction. Four places remain. Since pain is 3 or more, the order is water, rest in the warmth, local heat, plan pain medicine, then breathing. Result of reco.build: emergency (securite), water (general), rest in the warmth (general), heat (general), plan pain medicine (plan). Second case: score 45 (moderate), good form, no pain, no sign: water, rest in the warmth, avoid the cold, sleep, regular meals. Verified by running reco.build from the repository.

What this algorithm cannot do

Validation. The gestures are general self-care markers chosen with the patient-administrator (the code says so: « order decided with the patient-administrator »). The code cites a safety rule (the project's care card: oxygen only if SpO2 is below 95% or below baseline, no routine oxygen) but no study. The thresholds (40, 70, 3/10, 38.3 °C, 92%, 5, 10, 1 point, 8 points, 20 people) are set without a validation study in the repository. The 38.3 °C fever is read from the project's emergency threshold table. The effects « for you » and « other people » are observed associations, presented as such. This is not a validated medical device and not medical advice.

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*)

Risk of a dip in form

The risk of a dip in form is a second score, softer and more frequent than the crisis risk. It estimates the probability of a drop in your daily functioning (sleep, energy, pain) within 48 hours. It is meant to help you act early, for example with rest, hydration and a calmer pace, before a possible crisis.

Where you see it: In the bot message « My crisis risk » (My watch screen), right below the crisis risk: a line « Risk of a dip in form: X% · level » with a green, orange or red icon, an explanatory sentence, then one bullet per signal that weighs. No other screen displays this score: not the app, the watch or the dashboard (the code only sends it to this message).

What the algorithm reads

The calculation, step by step

Collect today's signalsThese are the same signals as the crisis riskComputed once, then given to both scoresDoes a crisis risk exist foryou?yesnoThe risk of a dip in form iscomputed right afterNeither score is computed(condition without crisisprediction, or no usablefactor)Dampen the signals from before a transfusionIf a transfusion is recent, night oxygen, SpO2, hemoglobin,transfusion delay, crisis and pain days of the last 7 days arereducedReduction = 60% × e^(−days / 6), up to 21 daysAdd up the weightslogit = −1.4 + sum (weight × signal)Only 24 signals enter the sumThe weights are fixed, never learned, never multiplied by 1.6Convert to a percentageprobability = 1 / (1 + e^(−logit))score = probability × 100, roundedNo 99% cap and no post-transfusion ceilingWhich level?yesnoBelow 30: low (green). 30 to59: moderate (orange). 60 ormore: high (red)The level is always one ofthe threePick the 4 explanationsContribution of each signal = weight × valueKeep those whose absolute value reaches 0.15The 4 strongest, by decreasing absolute valueWrite the sentencesPositive contribution: the unfavorable-direction sentence (« Yoursleep is shorter than usual »)Negative contribution: the favorable-direction sentenceThen: « this raises » or « this lowers your risk of a dip in form»
Flowchart: Risk of a dip in form

The values used

Name in the codeValueRoleWhere
FORM_BIAS−1.4Starting point of the calculation: a day with all signals at 0 gives 20% (1 / (1 + e^1.4) = 0.198), versus about 10% for crisissrc/trevor/wearables/crisis.py:605
moderate thresholdscore ≥ 30The thresholds are hard-coded in form_risk, with the same values as the crisis risksrc/trevor/wearables/crisis.py:615
high thresholdscore ≥ 60« High » levelsrc/trevor/wearables/crisis.py:615
contribution display threshold0.15A signal whose |weight × value| is below 0.15 is not explainedsrc/trevor/wearables/crisis.py:616
number of explanations4At most four signals shownsrc/trevor/wearables/crisis.py:617
STALE_SHARE0.6Share erased from pre-transfusion signals on the day itself (× e^(−days/6) afterward)src/trevor/wearables/crisis.py:529
keys dampened after transfusionz_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_days7Among them, only z_spo2, hb_low, transfusion_overdue, crisis_last7, pain_days7 and night_abs_low weigh in the dip in formsrc/trevor/wearables/crisis.py:524-528
TX_RECENT_DAYS / TX_RECENT_WINDOW6 days; 21 daystx_recent = e^(−days/6) for 21 days after the transfusion, then 0src/trevor/wearables/crisis.py:270-271, 292-293
hold_days5 days per unit, from 14 to 56 days; 28 without a unit countHolding time of the transfusion, which sets tx_protect and tx_losssrc/trevor/wearables/crisis.py:268-279
hb_low(9.5 − Hb) / 2, between 0 and 4Hemoglobin below 9.5 g/dL; 7.5 g/dL gives 1src/trevor/wearables/crisis.py:651-660
transfusion_overduedelay (fraction of interval) × 2, between 0 and 4Only for a person on a transfusion programsrc/trevor/wearables/crisis.py:658-659
cold_ambient(10 °C − tmin) / 10, between 0 and 4Cold below 10 °Csrc/trevor/wearables/crisis.py:104, 251
heat_ambient(tmax − 28 °C) / 6, between 0 and 4Heat above 28 °Csrc/trevor/wearables/crisis.py:105, 252
air_pm25(PM2.5 − 15) / 15, between 0 and 4Fine particles above the WHO guideline (15 µg/m³ over 24 h)src/trevor/wearables/crisis.py:107, 255

The weights

A positive weight raises the result, a negative weight lowers it.

SignalWeightWhere
Recent reported pain (highest of the last 3 days / 10)+0.8src/trevor/wearables/crisis.py:599
Fading effect of the last transfusion+0.8src/trevor/wearables/crisis.py:602
Sleep shorter than your normal (negative z)−0.6src/trevor/wearables/crisis.py:597
Activity lower than your normal (negative z)−0.5src/trevor/wearables/crisis.py:597
Low hemoglobin+0.5src/trevor/wearables/crisis.py:599
Measured stress higher than your normal+0.45src/trevor/wearables/crisis.py:597
HRV lower than your normal (negative z)−0.4src/trevor/wearables/crisis.py:597
Resting HR faster than your normal+0.4src/trevor/wearables/crisis.py:597
Crisis in the last 7 days+0.4src/trevor/wearables/crisis.py:599
Pain days in the week (0 to 2)+0.4src/trevor/wearables/crisis.py:600
Sleep over 3 days (negative z)−0.35src/trevor/wearables/crisis.py:598
Overdue transfusion+0.35src/trevor/wearables/crisis.py:599
SpO2 vs your normal (negative z)−0.3src/trevor/wearables/crisis.py:597
Hydroxyurea followed irregularly+0.3src/trevor/wearables/crisis.py:602
Consecutive nights with low oxygen (0 to 4)+0.25src/trevor/wearables/crisis.py:600
Skin temperature vs your normal+0.2src/trevor/wearables/crisis.py:597
HRV over 3 days (negative z); resting HR over 3 days−0.2 ; +0.2src/trevor/wearables/crisis.py:598
Outdoor cold; outdoor heat; PM2.5+0.2 eachsrc/trevor/wearables/crisis.py:603
Hydroxyurea taken regularly−0.2src/trevor/wearables/crisis.py:602
Recent transfusion; transfusion still holding−1.5 eachsrc/trevor/wearables/crisis.py:601

What is shown

A percentage from 0 to 100, a level and up to 4 explanations. Levels: low below 30% (green icon), moderate 30 to 59% (orange), high from 60% (red). The bot writes « Risk of a dip in form: X% · level », then « A possible drop in your form (sleep, energy, pain) within 48 h, lighter than a crisis », then one bullet per explanation, for example « Your sleep is shorter than usual: this raises your risk of a dip in form. ». The direction of the signals for form is carried by the weights: a negative sleep z multiplied by −0.6 gives a positive contribution.

A worked example

With no gap at all, every signal is 0 and the score is 20% (low level). Take a day where sleep is at −2 spreads, activity at −1.5, HRV at −1, resting HR at +1, stress at +1 and recent pain at 0.5 (5/10). The logit is −1.4 + 1.2 (sleep) + 0.75 (steps) + 0.4 (HRV) + 0.4 (HR) + 0.45 (stress) + 0.4 (pain) = 2.2. The probability is 1 / (1 + e^−2.2) = 0.90, i.e. 90% (high level). The four explanations kept are sleep (1.2), steps (0.75), stress (0.45), then HRV (0.4), which ties with HR and pain at 0.4 and comes first in the weight table order. Result obtained by running form_risk from the repository (score 90, level high, contributions z_sleep 1.2; z_steps 0.75; z_stress 0.45; z_hrv 0.4).

What this algorithm cannot do

Validation. The weights are a prior set by the designer: the code describes them as « prior weights, transparent », without citing a source or a comparison with observations. The sign of each weight follows plausible clinical reasoning (less sleep, less activity, lower HRV, faster heart, stress, pain and returning anemia push the score up), but no value was fitted on data. The base level (−1.4) and the thresholds of 30 and 60 are arbitrary. No internal or external study is cited for this score in the code or in the repository documents. This is not a validated medical device.

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)

Watch signals and temperature and heart sources

Trévor reads three families of signals from your watch, ring or files. The factual signals are simple markers (SpO2 below 92%, resting HR 10 bpm higher, short sleep, etc.) shown with their date. Three temperature and heart features enter the crisis risk with modest weights, and the signals module compares temperature and heart with your baseline for its own screens.

Where you see it: The factual signals appear in the watch summary (« My last 7 days ») and in the caregivers' dashboard as « Connected device: night SpO2 91% ». The three risk features are read in the sentences of the crisis risk, with below them the line « where the temperature and heart come from » (source, type, method, quality). The Signals screen shows temperature and heart against your baseline.

What the algorithm reads

The calculation, step by step

Factual signals (last 7 days)Computed only if interpretation is onBaseline = the 30 days before these 7 days, at least 5 valuesOne signal per concerned dayOxygen and heartNight SpO2 below 92%Resting HR at least 10 bpmabove the medianHRV at least 25% below themedianTemperature, breathing, sleepSkin temperature +0.5 °C ormoreRespiratory rate 20/min ormoreLess than 5 h of sleep 3nights in a row (a night under2 h is ignored)Entering the crisis risk: 3 featuresz_night_hr: lowest night HR against your baselinetemp_nights: consecutive nights (4 at most) with a skin deviationof at least 0.3 °Ctemp_hr_coupling: cautious product of the temperature rise and theHR rise, from 0 to 3Data missing?yesnoThe feature is 0: the modelkeeps its previous behaviorIt is multiplied by its priorweight (0.3; 0.25; 0.3), thenby 1.6Complete measured days with other sourcesImported lowest night HR and skin deviation complete a day alreadymeasured by the watchThey never replace an existing watch valueThey do not create a dayShow the originWhen temperature or heart weigh in the risk, up to 6 sources arelisted: source, type, method, quality7-day window
Flowchart: Watch signals and temperature and heart sources

The values used

Name in the codeValueRoleWhere
Low nighttime SpO2< 92Common alert threshold of consumer oximeters, according to the codesrc/trevor/wearables/service.py:486
High resting HR≥ median of the previous 30 days + 10 bpm (at least 5 values)Signal of a faster resting heartsrc/trevor/wearables/service.py:488
Low HRV≤ 0.75 × median (at least 5 values)Drop of 25% or moresrc/trevor/wearables/service.py:490
high skin temperaturedeviation ≥ +0.5 °CPossible fever signal (Oura ring, according to the code)src/trevor/wearables/service.py:492
high respiratory rate≥ 20 per minuteUsual adult polypnea threshold, according to the codesrc/trevor/wearables/service.py:494
short sleep< 5 h three nights in a row; a night < 2 h is ignoredA night under 2 h is probably a device not wornsrc/trevor/wearables/service.py:497-506
ELEVATED_TEMP_DELTA0.3 °CSkin deviation threshold to count a night as elevated in temp_nights (same value as in data/signaux_sources.json)src/trevor/wearables/crisis_signaux.py:20
MAX_NIGHTS4Cap on the number of consecutive nights countedsrc/trevor/wearables/crisis_signaux.py:21
temp_hr_couplingmin(3; max(0; z_temp)) × min(3; max(0; z resting HR, z night HR)) / 3Computed if at least one elevated skin night or a positive temperature z; otherwise 0src/trevor/wearables/crisis_signaux.py:62-63
NEW_PRIOR_WEIGHTSz_night_hr 0.3; temp_nights 0.25; temp_hr_coupling 0.3Prior weights in the crisis risk (then multiplied by 1.6)src/trevor/wearables/crisis_signaux.py:18
signals module baseline30-day window; 3 nights minimum; +0.3 °C; +8 bpm; MAD floor 3 bpm; resting tachycardia 100 bpm; resting jump 20 bpmValues of the Signals module for its own screens. They are not used for the risk scoredata/signaux_sources.json (baseline) ; src/trevor/signaux/baseline.py:15-24
measured feverthreshold from the emergency table (38.3 °C), thermometer onlyA sensor's skin temperature never triggers the emergency alonesrc/trevor/signaux/temperature.py:1-9, 32-34

The weights

A positive weight raises the result, a negative weight lowers it.

SignalWeightWhere
Lowest night HR vs your normal+0.3src/trevor/wearables/crisis_signaux.py:18
High skin temperature for several nights+0.25src/trevor/wearables/crisis_signaux.py:18
Temperature and HR rising together+0.3src/trevor/wearables/crisis_signaux.py:18
Skin temperature vs your normal (crisis risk)+0.5src/trevor/wearables/crisis.py:81
Fever sign (skin deviation ≥ 0.5 °C)+0.9src/trevor/wearables/crisis.py:83
Breathing vs your normal+0.4src/trevor/wearables/crisis.py:85

What is shown

For the factual signals: a list of dated lines, for example « Night SpO2 91% », « Resting HR 72 bpm (baseline 60) », « HRV 38 ms (baseline 52, drop of 25% or more) », « Skin temperature +0.6 °C », « Respiratory rate 21 /min », « Less than 5 h of sleep 3 nights in a row », from most recent to oldest. For the crisis risk: three numbers (z of lowest night HR, consecutive nights from 0 to 4, coupling from 0 to 3) added to the calculation, and the list of sources (up to 6). For the Signals screen: the gaps of temperature and heart from your normal. ECGs do not enter the score: they only trigger guidance messages.

A worked example

Resting HR: median of the previous 30 days at 60 bpm. A day at 70 bpm gives the signal « Resting HR 70 bpm (baseline 60) », because 70 ≥ 60 + 10. A day at 69 bpm does not. For the coupling, with a temperature z of +2 and a night HR z of +3: min(3; 2) × min(3; 3) / 3 = 2. Contribution to the crisis risk before amplification: 0.3 × 2 = 0.6; after the 1.6 gain: 0.96 on the model scale. Calculated by hand from the code formulas, not executed.

What this algorithm cannot do

Validation. The factual signal thresholds are common markers that the code cites without a reference: SpO2 of 92% (consumer oximeter threshold), polypnea at 20 per minute (early-warning scores), HRV drop of 25%. The weights of the three temperature and heart features (0.3; 0.25; 0.3) are « modest » priors: the code describes them as « plausible associations, not validated by a study on sickle cell disease », documented in docs/SIGNAUX_TEMPERATURE_COEUR_ECG.md, and they are readjusted on the person's data and then the cohort's. The 0.3 °C threshold and those of the Signals module are set without a calibration study. This is not a validated medical device.

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)

Vasoconstriction trend (experimental)

This indicative index spots skin that cools more than usual during the night, a possible sign of vasoconstriction. It compares your skin temperature, resting heart rate, HRV, oxygen and breathing with your previous 14 days. It stays silent when the measurement is fragile, and it is only computed if you gave your consent.

Where you see it: On the watch (My health screen), as codes only (state, level, factor names, quality), and as a « Skin cooling down (experimental) » factor in the crisis risk explanations when it weighs. This index displays no health value.

What the algorithm reads

The calculation, step by step

Consent given and indicatoron?yesnoThe last 16 days from thewatch are readNothing is computed: thefactor is 0 in the riskMeasurement quality (14 days)A day is usable with at least 2 useful measurements and sleep ofat least 2 h if knownGood: 10 or more usable days and one of the last two days usableMedium: 6 to 9 days under the same conditions. OtherwiseinsufficientInsufficient quality, or daynot usable?yesnoState « silence »: noconclusionContinue with today, oryesterday if missingDid the skin cool down?yesnoFactor « temp »: a drop of atleast 0.3 °C and at least 1spread from the median of theprevious 14 days (at least 5values)State « calm », no factorretainedOther factors (if the skin cooled)Heart: +5 bpm and at least 1 spreadHRV: at most 75% of the medianSpO2: −1.5 points and at least 1 spreadBreathing: +2 per minute and at least 1 spreadAt least 2 factors and «good » quality?yesnoState « clear », level 2State « slight », level 1(skin alone is enough)Entering the crisis riskValue 0, 1 or 2 depending on the level; 0 for silence and calmPrior weight +0.2, multiplied by 1.6: +0.32 per levelNever learned in training: 0 on training days
Flowchart: Vasoconstriction trend (experimental)

The values used

Name in the codeValueRoleWhere
BASE_DAYS14Previous days that form the comparison base (median)src/trevor/wearables/vasoconstriction.py:33
MIN_BASE5Minimum values in the base, for each measurementsrc/trevor/wearables/vasoconstriction.py:34
TEMP_DROP_C0.3 °CSkin drop from the median (in addition to at least 1 spread)src/trevor/wearables/vasoconstriction.py:35
TEMP_MIN_SD0.15 °CSpread floor for temperaturesrc/trevor/wearables/vasoconstriction.py:36
HR_RISE_BPM / HR_MIN_SD5 bpm ; 2 bpmRise of resting heart rate and spread floorsrc/trevor/wearables/vasoconstriction.py:37-38
HRV_RATIO0.75HRV at most 75% of the median (25% below it)src/trevor/wearables/vasoconstriction.py:39
SPO2_DROP1.5 pointsOxygen drop below the base (spread floor 0.7)src/trevor/wearables/vasoconstriction.py:40, 113
RESP_RISE2 per minuteRise of respiratory rate (spread floor 1.0)src/trevor/wearables/vasoconstriction.py:41, 115
Z_MIN1.0Each gap must also reach one robust spreadsrc/trevor/wearables/vasoconstriction.py:42
WINDOW_DAYS / GOOD_DAYS / MEDIUM_DAYS14 ; 10 ; 6Usable days to judge measurement qualitysrc/trevor/wearables/mesure.py:16-20
MIN_NIGHT_COVERAGE_MIN120A known sleep of under 2 h makes the day unusablesrc/trevor/wearables/mesure.py:17, 30
weight in crisis risk+0.2 (× 1.6 = +0.32 per level)Modest weight: the code speaks of a described association (Mohan 1998, Veluswamy 2020), not a validated predictionsrc/trevor/wearables/crisis.py:92
consentementctx_vasoconstriction_enabled and consent "My context"Without either, the result is empty (None) and the factor is 0src/trevor/wearables/vasoconstriction.py:134-141

The weights

A positive weight raises the result, a negative weight lowers it.

SignalWeightWhere
Skin cooling down (level 0, 1 or 2)+0.2src/trevor/wearables/crisis.py:92

What is shown

A result with a state (silence, calm, slight or clear), a level from 0 to 2, the names of the factors present (temp, hr, hrv, spo2, resp), the measurement quality (good, medium, insufficient) and the number of usable days. Only labels come out, never a health value or series, and nothing is logged. For the crisis risk, only the level counts: 0 for silence and calm, 1 for slight, 2 for clear. In the app and on the watch, the codes are silent, calm, slight and clear.

A worked example

Fourteen reference days: skin around 34.0 °C, resting heart rate 58 to 59 bpm, HRV 50 to 52, SpO2 96, breathing 14, sleep 420 min. Today: skin 33.5 °C and heart 66 bpm. Skin dropped by 0.5 °C (at least 0.3) and by more than one spread, and the heart rises by more than 5 bpm: two factors (temp, hr) with good quality, so state « clear », level 2, i.e. +0.64 on the model scale (2 × 0.32). If the heart stays at 59 bpm, only « temp » is present: state « slight », level 1. Result verified by running compute() from the repository (clear, level 2, factors temp and hr; slight, level 1, factor temp).

What this algorithm cannot do

Validation. The index is experimental and indicative, and the code says so: « neither a diagnosis nor a medical measurement ». It relies on associations described in three references the code cites: Mohan J et al., Clin Sci 1998;94(2):111-120 (stronger reflex vasoconstriction to mild cooling in SS sickle cell disease); Veluswamy S et al., Blood 2020 (vascular dysautonomia with thermal stimuli); Vuong C et al., Front Digit Health 2023 (watch HR and steps associated with pain, small study, crisis prediction not established). These studies involve controlled cooling tests or small samples, not wrist skin during the night. The thresholds (0.3 °C, 5 bpm, 75%, 1.5 points, 2 per minute) are set without a calibration study. Medical validation is pending (docs/VALIDATION_MEDICALE_EN_ATTENTE.md, M-F1). This is not a validated medical device.

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)

Important. These calculations guide you; they make no diagnosis and do not replace the care team's advice. In case of severe pain, fever or shortness of breath, call 144 without waiting.