Treatments and questionnaires
Treatment regularity, questionnaire scores and rule-based reminders.
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.
Personal associations
Trévor looks in your own data for cautious links between what you do or what surrounds you (sleep, weather, treatments, actions) and your pain, mood, form, fatigue or crises. A displayed link is not a cause.
Where you see it: Personal navigator dashboard, associations section.
What the algorithm reads
- Aligned daily series: pain, mood, form, fatigue, crisis, body, environment, doses, actions (self-report, watch, import, weather)
- Weekly questionnaire scores (PROMs, PREMs) (questionnaires)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
LAGS | (0, 1, 2, 3) | Lags tested in days | src/trevor/navigator/findings.py:20 |
MIN_PAIRS | 24 | Minimum paired days | src/trevor/navigator/findings.py:21 |
MIN_ABS_RHO | 0.25 | Minimum |rho| (day) | src/trevor/navigator/findings.py:22 |
MAX_Q | 0.10 | Maximum false discovery rate | src/trevor/navigator/findings.py:23 |
MAX_FINDINGS | 5 | Total associations | src/trevor/navigator/findings.py:24 |
MAX_PER_OUTCOME | 2 | Associations per outcome | src/trevor/navigator/findings.py:25 |
MIN_WEEKS | 8 | Minimum weeks (weekly scale) | src/trevor/navigator/findings.py:26 |
MIN_ABS_RHO_WEEKLY | 0.5 | Minimum |rho| (week) | src/trevor/navigator/findings.py:27 |
What is shown
At most 5 daily then weekly associations (computed only from 21 days of data, navigator/api.py:11), each with lag, rho, n, p and q values, confidence interval.
A worked example
No numeric example was run (the calculation needs real series).
What this algorithm cannot do
- An association is not a cause, and missing days are not zeros.
- Values declared by exception are not used as evidence.
- Short series from a single person keep some risk of chance findings, even with the controls.
- The chosen lag is the most favorable one, which is corrected only by the factor of 4.
Code : src/trevor/navigator/findings.py (daily_findings, weekly_findings, compute), src/trevor/navigator/stats.py, src/trevor/navigator/series.py (build)
Personalized screening calendar
Trévor reads a rules table (age, genotype, treatment) to tell you which monitoring exams recur at which pace, and when the next one is due based on the last date you enter. It organizes your follow-up, interprets no result and does not replace your care team.
Where you see it: "Screening calendar" screen (behind the prevention follow-up), for sickle-cell-type conditions; a child followed by a relative has their own calendar.
What the algorithm reads
- Genotype (SS, SC, Sb0, Sb+, other, unknown) (health record (medical history), child’s profile)
- Birth date or birth year (carte de soins, profil, foyer)
- Treatment: hydroxyurea, transfusion (self-report)
- Date and result (normal, to watch, abnormal) of each exam done, or "my team told me otherwise" (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
SOON_DAYS | 60 | Notice: "soon" when 60 days or fewer remain | src/trevor/lifecourse/screening.py:26 |
transcranial Doppler | 12 months, SS and Sβ0, ages 2 to 16 | Rhythm for these genotypes | data/screening_rules.json (exam doppler) |
Brain MRI | once, SS and Sβ0, from age 5 | "Once" rhythm | data/screening_rules.json (exam irm_cerebrale) |
eye exam, albuminuria | 12 months, from age 10 | All genotypes | data/screening_rules.json |
cardiac ultrasound | 36 months, from age 18 | Pace per HUG; divergence with ASH 2019 flagged in the file note | data/screening_rules.json |
ferritine | 3 months if transfusion, otherwise 12 months | Two rules in order | data/screening_rules.json |
ONCE_MONTHS | 1200 | A "once" exam counts as a century in the prevention plan | src/trevor/lifecourse/screening.py:25 |
What is shown
One line per exam with its pace, its state (up to date, soon, overdue, never, done, not recommended, not yet, team decides…), the due date and the last noted result. The prevention plan receives directives: drop, tighten the pace or pause reminders.
A worked example
Profile SS, age 20, hydroxyurea, eye exam entered on 12/20/2025: due 12/20/2026, 44 days left on 11/06/2026, so "soon" (calendar run). The Doppler does not apply at 20, the cardiac ultrasound and albuminuria are "never".
What this algorithm cannot do
- With the birth year only, age is a range: a minimum-age rule is matched against the highest age.
- An unknown genotype makes the brain exams say "genotype needed".
- The table is "to be validated by the HUG hematology department" before publication.
- Trévor does not interpret the result: you enter "normal", "to watch" or "abnormal" yourself.
Code : src/trevor/lifecourse/screening.py (rule_for, calendar, remindable, prevention_directives, age_range), src/trevor/lifecourse/screening_store.py (profile_for, calendar_for, directives), src/trevor/conversation/screening_reminders.py, data/screening_rules.json
Stress load and resources ("balance")
Trévor adds load points (stress, mood, PHQ-9, GAD-7, life events) and resource points (good things noted, supports, mood, calm) over 30 days and shows them side by side as two percentages. It is a project index with no clinical validation.
Where you see it: "My mental health" > Go further > Trends.
What the algorithm reads
- Average of your stress (0 to 10) and mood (0 to 10) over 30 days (self-report)
- Your latest PHQ-9 and GAD-7 scores (self-report)
- Good-thing notes over 30 days, number of supports, life events (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
maximum load | 8 | Denominator of the load percentage | src/trevor/conversation/mental_health.py:755 |
maximum resources | 6 | Denominator of the resources percentage | src/trevor/conversation/mental_health.py:756 |
level thresholds | 60 and 30 | Level limits for load and resources | src/trevor/conversation/mental_health.py:757-758 |
window | 30 days | Period of averages and notes | src/trevor/conversation/mental_health.py:702 |
What is shown
Two levels and two percentages, the list of factors that weigh and those that protect, and a piece of advice. With no data at all, a message says there is not enough data.
A worked example
Average stress 5 (+1), average mood 4 (+2), PHQ-9 of 12 (+2), GAD-7 of 3 (0), no event: load 5 points out of 8, so 62% (62.5 rounded to 62), high level (checked by calculation).
What this algorithm cannot do
- Points and thresholds are set without calibration: a single data point is enough to show a percentage.
- The PHQ-9 and GAD-7 used are the latest ones, whatever their age.
- A "low" load level can come from missing data, not from good health.
- It is not a validated medical device and does not replace professional advice.
Code : src/trevor/conversation/mental_health.py (allostatic_balance, _balance_block, finish_mh_mood)
Short screeners: post-traumatic stress, ADHD, autism
Trévor counts your answers to three short questionnaires (PC-PTSD-5, ASRS v1.1 part A, AQ-10) and says whether the screen is "positive", which means a professional opinion is suggested. This is not a diagnosis or a validated medical device: the result points you toward a professional.
Where you see it: "My mental health" > Self-check.
What the algorithm reads
- PC-PTSD-5: 1 exposure question then 5 yes/no items (self-report)
- ASRS: 6 answers (Never, Rarely, Sometimes, Often, Very often) (self-report)
- AQ-10: 10 agree / disagree answers (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
ASRS_THRESHOLD | {0: 2, 1: 2, 2: 2, 3: 3, 4: 3, 5: 3} | Minimum answer position for the item to count | src/trevor/conversation/mental_health.py:58 |
ASRS threshold | 4 out of 6 | Positive screen from 4 items | src/trevor/conversation/mental_health.py:191-195 |
AQ_AGREE_ITEMS / AQ_DISAGREE_ITEMS | {0,6,7,9} / {1,2,3,4,5,8} | Items scored when you agree, or disagree (Allison 2012 key per the code) | src/trevor/conversation/mental_health.py:60-61 |
AQ-10 threshold | 6 | Positive screen from 6 points | src/trevor/conversation/mental_health.py:688 |
PC-PTSD-5 threshold | 3 | Positive screen from 3 "Yes" | src/trevor/conversation/mental_health.py:662 |
What is shown
A score (PC-PTSD-5 out of 5, ASRS out of 6, AQ-10 out of 10) and a "positive" or "negative" message with an explanation. This is not a diagnosis or a validated medical device: the result points you toward a professional.
A worked example
No numeric example was run for this card.
What this algorithm cannot do
- These are short screeners: a positive result does not say you have the condition, a negative does not rule it out.
- The PC-PTSD-5 exposure question is read from the first exact "Yes" answer.
- The ASRS uses only part A (6 questions).
Code : src/trevor/conversation/mental_health.py (pcptsd_count, asrs_positive, aq10_score, _finish_pcptsd, _finish_asrs, _finish_aq10)
Depression and anxiety: PHQ-9, GAD-7 and Edinburgh scale
Trévor adds up your answers to the PHQ-9 (mood), GAD-7 (anxiety) and, during the postpartum period, the Edinburgh scale (EPDS). It gives a score, a level and advice, and always handles first any answer that suggests thoughts of self-harm. This is not a diagnosis or a validated medical device: the result points you toward a professional.
Where you see it: "My mental health" > Self-check. The EPDS comes first if you are postpartum.
What the algorithm reads
- Your answers to the 9 PHQ-9 or 7 GAD-7 questions (Never, Several days, More than half the days, Nearly every day) (self-report)
- Your answers to the 10 EPDS questions (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
FREQ_SCALE | [Never, Several days, More than half the days, Nearly every day] | PHQ-9 and GAD-7 answer scale (0 to 3) | src/trevor/conversation/mental_health.py:53 |
EPDS_REVERSED_ITEMS | {2, 4, 5, 6, 7, 8, 9} | Reversed EPDS items (0-based indices) | src/trevor/conversation/mental_health.py:64 |
EPDS_POSSIBLE, EPDS_PROBABLE | 10, 13 | EPDS thresholds | src/trevor/conversation/mental_health.py:65 |
PHQ-9 thresholds | 4 / 9 / 14 / 19 | Upper limits of the minimal, mild, moderate, moderately severe levels | src/trevor/conversation/mental_health.py:169-178 |
GAD-7 thresholds | 4 / 9 / 14 | Upper limits of the minimal, mild, moderate levels | src/trevor/conversation/mental_health.py:181-188 |
advice thresholds | 15 and 10 | "See a professional" advice from 15, intermediate advice from 10 | src/trevor/conversation/mental_health.py:605-609 |
What is shown
A score (PHQ-9 out of 27, GAD-7 out of 21, EPDS out of 30), its level in words and advice. Answers suggesting suicidal thoughts trigger a support message with your country's helplines before anything else. This is not a diagnosis or a validated medical device: the result points you toward a professional.
A worked example
PHQ-9 with "Several days" for all 9 questions: 9, mild level; with 5 answers "More than half the days" and 4 "Several days": 14, moderate level (checked by running phq9_score). GAD-7 with "Several days" everywhere: 7, mild level. EPDS: score 11 → "possible depression".
What this algorithm cannot do
- The GAD-7 has no suicidal-thought check: only the PHQ-9 (question 9) and the EPDS (question 10) have one.
- An answer outside the expected labels (another language or edited text) makes the score impossible.
- The score is computed on the displayed French labels; I did not check here how other languages map to those labels.
- Trévor makes no diagnosis and does not replace a clinical assessment.
Code : src/trevor/conversation/mental_health.py (phq9_score, gad7_score, phq9_band, gad7_band, epds_item_scores, epds_band, _finish_phq9, _finish_gad7, _finish_epds)
Hydroxyurea follow-up test reminder
Trévor works out the date of your next follow-up test by adding the pace your doctor gave you to the date of the last known test. It never suggests a pace itself and sends no reminder until you have entered one.
Where you see it: "Regularity and follow-up tests" screen (test line), and a reminder message attached to the gentle prevention reminder.
What the algorithm reads
- Test pace: 1, 2, 3, 4 or 6 months, or "not known yet" (self-report (yorr choice))
- Date of the last test noted by hand (self-report)
- HbF and hemoglobin results in your record (measurement date) (health record / import)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
rhythm_months | [1, 2, 3, 4, 6] | Accepted paces; any other number is refused | data/hydroxyurea.json:12 |
soon_days | 30 | Notice: "soon" when 30 days or fewer remain | data/hydroxyurea.json:13 |
kinds | ["hbf", "hb"] | Record measurement types that count as a test | data/hydroxyurea.json:14 |
date limit | 5 years | A noted test cannot be in the future or more than 5 years old | src/trevor/hydroxyurea.py:135 |
What is shown
Five states: unset, no date, up to date, soon (30 days or fewer), overdue. Each has its own sentence. All except "up to date" and "unset" trigger a single reminder per due date.
A worked example
Pace 3 months, last test on 08/15/2026: due on 11/15/2026; on 11/06/2026 there are 9 days left, so "soon" (computed by hand from add_months and the 30-day threshold).
What this algorithm cannot do
- The pace is what you enter: Trévor does not check it.
- An imported hemoglobin or HbF measurement counts as a follow-up test even if it came from another context.
- The reminder says nothing about the values.
- The proposed paces (1, 2, 3, 4, 6 months) have not been reviewed by the HUG.
Code : src/trevor/hydroxyurea.py (labs_status, last_lab_date, add_months, reminder_text, reminder_pending, mark_labs_done, set_rhythm), src/trevor/conversation/reminders.py (prevention_due_reminders), data/hydroxyurea.json
Hydroxyurea in the crisis risk
When you take hydroxyurea, your regularity over the last 84 days becomes two features of the risk model: a protective one and an "irregular intake" one. This card describes only that input, not the full risk calculation, which has its own card.
Where you see it: Indirectly: in the reasons shown with the crisis risk (for example "Hydroxyurea taken irregularly").
What the algorithm reads
- Your regularity over 84 days (whole percentage) (self-report, assumed doses included)
- Having hydroxyurea among your treatments (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
good threshold | 80 | From 80%: full protection | src/trevor/wearables/crisis.py:297 |
medium threshold | 60 | 60 to 79%: protection 0.6 and gap 0.4 | src/trevor/wearables/crisis.py:299 |
RISK_GAIN | 1.6 | Gain applied to the other risk factors | src/trevor/wearables/crisis.py:493 |
GAIN_EXEMPT | hu_protect, dmt_other, tx_recent, tx_protect | Protective features that are not amplified | src/trevor/wearables/crisis.py:495 |
The weights
A positive weight raises the result, a negative weight lowers it.
| Signal | Weight | Where |
|---|---|---|
| Hydroxyurea taken regularly (≥ 80% or unknown) | −0.55 | src/trevor/wearables/crisis.py:78 |
| Hydroxyurea taken irregularly | +0.45 | src/trevor/wearables/crisis.py:78 |
What is shown
Two numbers between 0 and 1 (hu_protect and hu_gap) that enter the model sum. An "hydroxyurea" reason appears with the risk if it weighs enough.
A worked example
Regularity 79%: hu_protect 0.6, hu_gap 0.4; 59%: 0 and 1; 80%: 1 and 0 (checked by running treatment_features).
What this algorithm cannot do
- The 80% and 60% thresholds are hard-coded in crisis.py, while the Regularity card reads 80% from data/hydroxyurea.json: two places to keep identical by hand.
- The live calculation (treatment_context) gives hu_protect = 1 when you take hydroxyurea without a computable regularity, while training (training_rows) sets both features to 0 on those days: the protective prior therefore applies only to the live display.
- Regularity includes assumed doses: a person who never reports a missed dose stays in the protective band.
- Going from 79% to 80% makes the feature jump from 0.6 to 1: the curve is stepwise, not continuous.
Code : src/trevor/wearables/crisis.py (treatment_features, PRIOR_WEIGHTS, GAIN_EXEMPT), src/trevor/wearables/service.py (treatment_context, training_rows), src/trevor/hydroxyurea.py (pdc, pdc_from_days)
Hydroxyurea regularity (days covered)
Trévor counts, over the last 12 weeks, the share of logged days on which you took your hydroxyurea. This percentage helps you see how regular you are and feeds other indicators (crisis signal, crisis risk). It is not a validated medical device and it does not judge how you take your treatment.
Where you see it: The "Regularity and follow-up tests" screen of the bot and the /api/phone/hydroxyurea route of the app, for people who have hydroxyurea among their treatments.
What the algorithm reads
- Days logged as taken or missed for hydroxyurea, over 84 days (self-report (“taken” and “missed dose” actions) and doses assumed to be written at night)
- The list of your tracked treatments, or your pharmacy list (self-report / health record)
- Today's date (clock)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
window_days | 84 | Calculation window: 12 weeks, current day included | data/hydroxyurea.json:7 |
min_logged_days | 14 | Minimum logged days to show a percentage | data/hydroxyurea.json:8 |
good_from_percent | 80 | Threshold from which regularity is called "good" | data/hydroxyurea.json:9 |
a_valider | true | The parameter file declares itself "to be validated by the HUG" | data/hydroxyurea.json:4 |
What is shown
A whole-number percentage from 0 to 100 (or nothing under 14 logged days), the number of covered, logged and declared days, and a "good" flag from 80%. The 60% threshold does not exist here: it appears only in the crisis risk calculation (card "Hydroxyurea in the crisis risk").
A worked example
With 30 logged days of which 22 taken (one day in four missed), regularity = 22 ÷ 30 = 73% (checked by running pdc_from_days). With only 10 logged days, all taken, the result is "no percentage" because 10 < 14.
What this algorithm cannot do
- The calculation divides by logged days, not by the 84 days: an unlogged day disappears from the calculation without counting as missed.
- By default ("exception" mode), a "taken" row is written overnight for days without any action: the percentage is then almost always high until you report a missed dose. The display says so only when declared < logged.
- Only the last two elapsed days are filled in each night, and never before the first run: older history stays empty.
- The calculation knows neither the dose nor the time: "taken" means logged as taken.
- The 80% threshold and the 84-day window have not been reviewed by a clinician.
Code : src/trevor/hydroxyurea.py (pdc_from_days, pdc, hu_key, missed_reasons, lines), data/hydroxyurea.json, src/trevor/navigator/doses.py (fill_defaults), src/trevor/storage/database.py (adherence_days)
"Worth discussing with your team" signal (HbF or crises)
Trévor spots two situations worth raising with your care team: an HbF that stays under your own best level over three measurements, or at least three crises in six months despite regular intake. It never concludes that the treatment does not work and proposes no change.
Where you see it: "Regularity and follow-up tests" screen and consultation questions. The signal is never sent as a notification.
What the algorithm reads
- Your HbF measurements (value in % and date) (health record / import)
- Your declared crisis episodes (start day) (crisis log (self-report))
- Your regularity over 84 days, with at least 28 logged days (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
min_hbf_measures | 3 | Minimum HbF measurements | data/hydroxyurea.json:17 |
min_logged_days_for_adherence | 28 | Minimum logged days to judge regularity in this signal | data/hydroxyurea.json:18 |
crises_min | 3 | Number of crises that triggers the signal | data/hydroxyurea.json:19 |
crises_window_days | 180 | Crisis counting window (shown as "6 months") | data/hydroxyurea.json:20 |
good_from_percent | 80 | Same regularity threshold as the "Regularity" card | data/hydroxyurea.json:9 |
What is shown
A list of signals ("hbf", "crises", both or none). Each signal shows its reason (the number of crises is quoted) then the footer sentence "worth discussing with your care team". A consultation question is added if a signal exists.
A worked example
HbF logged 8, 12, 10, 9.5: last 9.5 < best 12 and 12 → 10 → 9.5 does not rise, so signal (checked with hbf_vs_best). With only 8 and 12, count = 2, no signal.
What this algorithm cannot do
- The comparison uses your own highest value, with no population norm: an unusually high first measurement makes the signal easy to trigger.
- No minimum gap between measurements is required: three measurements taken in the same month count.
- Counted crises are those in your journal; an undeclared crisis does not exist for this calculation.
- The "crises" signal looks only at regularity, not at dose or treatment duration.
Code : src/trevor/hydroxyurea.py (non_response, signal_lines, consult_questions), src/trevor/royal/labs.py (hbf_series, hbf_vs_best), data/hydroxyurea.json
How a day is classed as a "crisis" day
Trévor decides whether a day counts as a crisis day from what you logged: severe pain, hospitalization, a declared crisis episode. This label is then used to train the risk model, to spot trends and to test associations. The rules are not identical in every module.
Where you see it: Not shown as such: the label feeds the crisis risk, trends ("My indicators"), charts and associations. The crisis journal shows you the episodes you declare.
What the algorithm reads
- Daily pain from 0 to 10 (daily follow-up) (self-report)
- "Hospitalized" answer of the daily follow-up (self-report)
- Pain logged outside the questionnaire (chat, linked watch), with its text (self-report / watch)
- Crisis journal episodes: start, end, maximum pain, care level (self-report)
- Hospital stays (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
pain threshold | 7 | Pain from 0 to 10 that makes the day a crisis day | src/trevor/wearables/service.py:553 |
CRISIS_OBS_WORDS | ("crise", "urgence", "hospital", "cvo", "vaso") | Words that, with pain ≥ 7 logged outside the questionnaire, make the day a crisis | src/trevor/wearables/service.py:513 |
HORIZON_DAYS | 2 | The training label is 1 if a crisis occurs today or in the next 2 days | src/trevor/wearables/crisis.py:123 |
pain threshold (charts) | 7 | Same threshold in the chart and association series | src/trevor/navigator/series.py:157 |
circle alert threshold | 7 | Maximum pain from which the circle alert is offered | src/trevor/conversation/crisis_diary.py:178 |
What is shown
For each day: crisis yes/no, with the day's maximum pain. The risk training label also looks at the next 2 days.
A worked example
A day on which you log pain of 7/10: crisis day (7 ≥ 7). The day before and the one before that receive training label 1 when the signals of those days are learned. Pain of 6/10 with no hospitalization or episode is not a crisis day.
What this algorithm cannot do
- Modules do not apply the same rule: the risk model counts the "hospitalized" answer, charts do not (hospital stays only exclude assumed days), and trends look only at journal episodes.
- The threshold of 7 is the same everywhere but hard-coded in several places.
- In "exception" mode, a day with no report is shown with pain 0, with nothing written: it is never classed as a crisis.
- A crisis treated at home is considered over on the day it is saved.
- Free-text keywords are matched literally, without any analysis of meaning.
Code : src/trevor/wearables/service.py (day_records), src/trevor/wearables/crisis.py (training label), src/trevor/navigator/series.py (build), src/trevor/conversation/insights.py (_crisis_days), src/trevor/conversation/crisis_diary.py, src/trevor/dashboard/data.py (daily_series)
Reminder the day before your crisis-prone weekday
If at least 5 of your crises over the last 120 days cluster on the same weekday, Trévor spots that day so it can send a self-care reminder the day before. It relies only on your own journal.
Where you see it: Self-care reminder message the day before the dominant weekday. Reserved for patients whose condition has crisis prediction and who enabled crisis alerts; at most one message every 7 days (reminders.py:1416-1450).
What the algorithm reads
- Start dates of your crises (crisis log (self-report))
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
MIN_CRISES | 5 | Minimum crises | src/trevor/conversation/prevention.py:16 |
WINDOW_DAYS | 120 | Window | src/trevor/conversation/prevention.py:17 |
DOMINANCE | 1.5 | Share of dominant day in multiples of the uniform expectation (1/7) | src/trevor/conversation/prevention.py:18 |
What is shown
A weekday, or none.
A worked example
With 7 crises, the dominant day must have at least max(3; 1.5 × 7 ÷ 7) = 3. With 14 crises, at least max(3; 3) = 3; with 21 crises, at least 4.5 so 5 (computed by hand).
What this algorithm cannot do
- With 5 to 8 crises, three on the same day are enough: chance is possible.
- The calculation does not account for days on which you were more exposed (work, travel).
Code : src/trevor/conversation/prevention.py (dominant_crisis_weekday), src/trevor/conversation/insights.py
Pain check after a rescue painkiller
45 minutes after you log a rescue painkiller dose, Trévor asks how the pain is and applies a three-level escalation protocol: call the emergency number (144 in Switzerland), go to the ER, or medical advice within 24 hours. The thresholds remain to be validated by hematology. This is not a validated medical device.
Where you see it: Neutral notification with no mention of pain (lock screen), then a question with four answers: better, same, worse, danger sign.
What the algorithm reads
- The rescue painkiller dose logged (time) (self-report (bot, watch, phone))
- Your answer: better, same, worse, danger sign, pain gone, stop asking (self-report)
- Declared complications (stroke, pulmonary hypertension, kidney) (carte de soins)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
FIRST_CHECK | 45 min | First check after the dose | src/trevor/conversation/pain_check.py:35 |
SAME_RECHECK | 30 min | New check after "same" | src/trevor/conversation/pain_check.py:36 |
BETTER_RECHECK | 3 h | New check after "better" | src/trevor/conversation/pain_check.py:37 |
ESCALATE_AFTER | 1 h | Unchanged pain after one hour: level 2 | src/trevor/conversation/pain_check.py:38 |
DAY_CEILING | 20 h | Pain still there: level 3 | src/trevor/conversation/pain_check.py:39 |
EPISODE | 24 h | Duration of an episode | src/trevor/conversation/pain_check.py:40 |
HIGH_RISK | (stroke, pulmonary hypertension, renal) | Complications that send you to the ER at the first "same" | src/trevor/conversation/pain_check.py:41 |
What is shown
One message among: danger sign, worse, escalation, new check, better, 20-hour ceiling, pain gone, feature off. Your country's emergency number is inserted. Nothing is sent to a caregiver.
A worked example
Dose at 2:00 pm, check at 2:45 pm: "same" with 45 minutes elapsed and no complication → new check at 3:15 pm; "same" at 3:15 pm (1 h 15 elapsed) → ER (read from decide).
What this algorithm cannot do
- The time thresholds still need validation by hematology (docs/VALIDATION_MEDICALE_EN_ATTENTE.md).
- The complication list is read from the care card text: an unwritten complication does not lower the threshold.
- The first lock-screen message does not say why it is sent, to protect your privacy.
- Trévor gives no dose and does not replace medical advice.
Code : src/trevor/conversation/pain_check.py (arm, due, decide, _pain_high_risk), src/trevor/conversation/rescue.py (arm after a dose), src/trevor/conversation/reminders.py (pain_check_tick)
"Default" doses and treatment regularity rate
When you are in "exception" mode, Trévor shows your treatments as taken until you report a missed dose, and writes an "assumed" taken row overnight. The regularity rate of each treatment is then computed on logged days.
Where you see it: Treatments screen ("taken", "done" for a dressing), "My indicators", consultation summary, caregiver view.
What the algorithm reads
- Your actions: "taken", "missed dose" with its reason (self-report)
- Treatment mode and the rhythm of each treatment (daily or spaced) (self-report / catalog)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
CATCHUP_DAYS | 2 | Elapsed days filled in each night | src/trevor/navigator/doses.py:24 |
REASONS | (forgot, unwell, no_supply, side_effects, away, other) | Reasons for a missed dose | src/trevor/navigator/doses.py:23 |
rate window | 30 days (default) | Default window of the adherence rate | src/trevor/storage/database.py:6241 |
consultation threshold | 80 | Consultation question if the rate is under 80% with at least 7 logged days | src/trevor/conversation/consultation.py:42 |
What is shown
A state per day and per treatment (taken, assumed, missed with reason), a percentage rate, a streak of consecutive days and the number of assumed days.
A worked example
No numeric example was run for this card (the calculation needs a database).
What this algorithm cannot do
- The streak restarts from zero as long as the current day has no row: it counts only if today is already logged.
- The rate divides by logged days, not by window days.
- With default doses, a high rate may come from the absence of reports.
- The overnight catch-up covers only 2 days: if Trévor did not run for longer, those days stay empty.
Code : src/trevor/navigator/doses.py (state, fill_defaults, declare_missed), src/trevor/storage/database.py (adherence_days, adherence_summary), src/trevor/navigator/checkin.py (inferred_taken)
Questions to ask your doctor
Trévor prepares up to five questions for your appointment from your recent data: crises, regularity, hydroxyurea signal, late transfusion, wound, overdue exams. Each question points to a topic to raise, never a diagnosis.
Where you see it: Consultation preparation and three points reused in team tasks (team_care.py:596-598).
What the algorithm reads
- Crises in the last 30 days and their care level (crisis log)
- Regularity rate of each treatment (assumed doses included) (self-report)
- Last transfusion and program interval (self-report)
- Leg wound, prevention exams (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
crises | 2 | Threshold for the frequency question | src/trevor/conversation/consultation.py:29 |
rated days / rate | 7 / 80 | Thresholds for the daily treatment question | src/trevor/conversation/consultation.py:42 |
transfusion delay | 1 week | Delay from which the question is asked | src/trevor/conversation/consultation.py:58 |
maximum | 5 | Maximum number of questions after filling | src/trevor/conversation/consultation.py:85 |
What is shown
A list of questions written in French, filled up to 5 with general questions when fewer are found.
A worked example
No numeric example was run.
What this algorithm cannot do
- The list can exceed 5 questions before filling: the cap of 5 only limits the added general questions.
- The sentences about ER, transfusion and wound are written in French in the code, outside the translation system.
- The rate includes assumed doses.
Code : src/trevor/conversation/consultation.py (suggested_questions), src/trevor/hydroxyurea.py (consult_questions)
Questionnaire scores: self-efficacy, quality of life, perceived usefulness
Trévor computes the score of several questionnaires: confidence in managing your condition (9 to 45), EQ-5D-5L quality of life, PedsQL quality of life (0 to 100) and perceived usefulness of Trévor (USE scale or short 3-question questionnaires). Each score is saved with your answer. These are not validated medical devices.
Where you see it: Result at the end of the condition-management questionnaire (with comparison to the average of people), quality-of-life questionnaire, category radar.
What the algorithm reads
- Choice answers (the choice text, never a number) (self-report)
- Numeric answers from 1 to 7 (perceived usefulness) (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
confidence bands | [[42,very high],[36,high],[27,medium],[0,low]] | Levels of the confidence score (out of 45) | data/questionnaires/diseaseManagement.json (scoring.bands) |
simplified EQ-5D-5L formula | 100 − 5 × sum(level − 1) | Fallback score when the national value set is absent | src/trevor/conversation/questionnaires/scoring.py:61-63 |
value set | data/scoring/eq5d5l_value_set.csv (not in the repository) | Licensed EuroQol file to be provided; the repository contains only the README | data/scoring/README.md |
PedsQL | 100 − position × 25 | Conversion of an answer to points | src/trevor/conversation/questionnaires/scoring.py:74 |
min_item / max_item | 1 / 7 | Bounds of perceived-usefulness items | data/questionnaires/useUtilite.json (scoring) |
What is shown
One number per questionnaire. The EQ-5D-5L is labeled "simplified" when the value set is absent.
A worked example
EQ-5D-5L with levels 1, 2, 3, 1, 4: penalty 0 + 1 + 2 + 0 + 3 = 6, simplified score 100 − 30 = 70 (checked by running eq5d5l_score).
What this algorithm cannot do
- The simplified EQ-5D-5L score is not the instrument's index: it cannot be compared with published values.
- Before October 4, 2026, everyone had the score of a person with no problem because the level was read as a number; a rescoring function exists for old answers.
- In self-efficacy, an unknown answer counts 0 points, which pulls the score down without the question being dropped.
- The official PedsQL has missing-score rules that this code does not apply: it averages whatever is answered.
- The "better or worse than average" comparison depends on the people who answered before you.
Code : src/trevor/conversation/questionnaires/scoring.py (compute_score, sum_map_score, band_label, category_label, eq5d5l_score, eq5d5l_levels, choice_level, pedsql_score, sum_numeric_score), src/trevor/conversation/display.py (format_self_efficacy_score), data/questionnaires/*.json (scoring)
Medicine stock and reorder reminder
Trévor estimates how many treatment days you have left for each medicine and offers to prepare an order when stock gets low. It sends nothing to the pharmacy without your agreement.
Where you see it: "My medicines" ("order", "soon", "ok"), and an order reminder message.
What the algorithm reads
- Days per box, number of boxes, date of last refill or count (self-report)
- Dates of orders sent (order history)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
LOW_DAYS | 7 | Order from 7 days or fewer | src/trevor/conversation/pharmacy.py:52 |
SOON_DAYS | 14 | Soon from 14 days or fewer | src/trevor/conversation/pharmacy.py:53 |
rappel | 5 days / 2 days | Gap between reminders / pause after an order | src/trevor/conversation/reminders.py:1470-1472 |
MAX_BOX_DAYS / MAX_BOXES | 365 / 99 | Entry bounds | src/trevor/conversation/pharmacy.py:54-55 |
What is shown
A number of days left, a level (order, soon, ok, unknown) and a reminder message that names the medicine and the number of days.
A worked example
Box of 30 days, 2 boxes, refill 55 days ago: 30 × 2 − 55 = 5 days left, level "order" (days_left calculation read from the code).
What this algorithm cannot do
- The calculation assumes you use exactly one box over the stated duration, ignoring missed days, changed doses or extra intake.
- Without a refill date, no reminder is possible.
- I found no check for drug interactions in the code: Trévor flags no interaction.
Code : src/trevor/conversation/pharmacy.py (days_left, level, to_order, public), src/trevor/conversation/reminders.py (pharmacy_nudges)
Perceived stress: PSS-10 scale and daily feeling
Trévor computes your score on the PSS-10 perceived stress scale (0 to 40) and gives it a level, then reacts to your daily feeling (0 to 10) with advice. The questionnaire cannot be retaken within 14 days. This is not a diagnosis or a validated medical device: the result points you toward a professional.
Where you see it: "My stress": daily feeling, PSS-10, trends.
What the algorithm reads
- 10 answers from Never to Very often, about the last month (self-report)
- Your stress level today, from 0 to 10 (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
PSS_SCALE | Never 0 ... Very often 4 | Points per answer | src/trevor/conversation/stress.py:45 |
PSS_REVERSED | {3, 4, 6, 7} | Reversed items (0-based indices) | src/trevor/conversation/stress.py:46 |
PSS-10 thresholds | 13 and 26 | Upper limits of the low and moderate levels | src/trevor/conversation/stress.py:82-87 |
minimum delay | 14 days | A new attempt is refused before 14 days | src/trevor/conversation/stress.py:141 |
What is shown
A score out of 40, a level and advice. The daily feeling gives advice and, from 8 out of 10, the helplines. This is not a diagnosis or a validated medical device: the result points you toward a professional.
A worked example
All answers "Sometimes": each item is 2, reversed 4 − 2 = 2, so 20 out of 40, moderate level (checked by running pss_score).
What this algorithm cannot do
- The language-model advice differs from one time to the next; only the simple-rule fallback is deterministic.
- The limits 13 and 26 are fixed in the code with no source.
- Exact labels are required: any other wording makes the score impossible.
Code : src/trevor/conversation/stress.py (pss_score, pss_band, finish_pss10, finish_stress_checkin, _stress_recommendation)
Spaced "how are you?" and assumed values
Trévor does not ask you every day how you are. By default it assumes all is well as long as you say nothing, and asks one short question from time to time, more spaced out when you answer "good". Assumed days are never used as evidence.
Where you see it: "How are you?" message with three answers (good, so-so, not good); assumed days are marked "assumed" in charts and tracking.
What the algorithm reads
- The mode of each domain: exception, daily or off (pain, mood, treatments, water) (self-report)
- Your latest follow-up, mood, pain, crisis or hospital stay (self-report)
- The number of reminders ignored in a row (reminder history)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
CHECKIN_EVERY_DAYS | 10 | Starting gap between two questions | src/trevor/navigator/checkin.py:26 |
CHECKIN_MAX_EVERY_DAYS | 28 | Maximum gap | src/trevor/navigator/checkin.py:27 |
CHECKIN_SCHEDULE | (10, 14, 21, 28) | Gap after 1, 2, 3, 4 "good" answers in a row | src/trevor/navigator/checkin.py:28 |
CHECKIN_AFTER_BAD_DAYS | 3 | Delay before the next question after "not good" | src/trevor/navigator/checkin.py:29 |
SILENCE_DAYS | 7 | No question if you logged anything within these days | src/trevor/navigator/checkin.py:30 |
LEVELS | {good: 8, mid: 5, bad: 3} | Mood out of 10 saved for each answer | src/trevor/navigator/checkin.py:31 |
reminder_misses | 3 | 3 or more ignored reminders: no more questions | src/trevor/navigator/checkin.py:181 |
What is shown
A spaced "how are you?" question, a mood out of 10 saved, and assumed "all is well" days (pain 0, mood 8, water at goal) displayed as assumed.
A worked example
First "good": gap of 10 days; second "good" in a row: 14 days; fourth and beyond: 28 days. A "not good" brings the next question back to 3 days (read from record_level).
What this algorithm cannot do
- No report is read as "all is well": an undeclared pain day stays at 0 in the assumed display.
- Assumed values (mood 8) are conventions, not measurements.
- Only three levels: "so-so" equals 5 out of 10, with no nuance.
- The code cites the motivation (drop in adherence to repeated assessments) but the numbers 10, 14, 21, 28 are design choices.
Code : src/trevor/navigator/checkin.py (mode, checkin_due, record_level, assumed_days, assumed_values), src/trevor/navigator/series.py (build)
Prevention follow-up, vaccines and exam reminders
For each monitoring exam or vaccine you follow, Trévor computes the due date (last date + interval) and a state: up to date, soon, overdue or never entered. It sends a gentle reminder, only one per due date.
Where you see it: Prevention follow-up (✅ 🟡 🔴 ⬜), "My vaccines" summary, reminder message.
What the algorithm reads
- Date of last exam or vaccine (self-report)
- Known complications on the care card (self-report (carte de soins))
- Pace given by your team (pauses the reminder) (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
PREVENTION_SOON_DAYS | 60 | Prevention follow-up notice | src/trevor/conversation/engine.py:2737 |
SOON_DAYS (vaccins) | 60 | Same notice for vaccines | src/trevor/lifecourse/vaccine_log.py:40 |
PLAN_MONTHS | flu 12; pneumococcus 60; meningococcus 60 | Usual pace of the plan's three vaccines | src/trevor/lifecourse/vaccine_log.py:38 |
REMINDER_MONTHS | (12, 60, 120) | Possible reminders of a vaccine you add | src/trevor/lifecourse/vaccine_log.py:39 |
_KDIGO_VISITS | G1 (1,1,2) ... G5 (4,4,4) | Checks per year by GFR and albuminuria | src/trevor/lifecourse/complications.py:110 |
What is shown
One line per exam or vaccine: last date, due date, state (up to date, soon, overdue, never), and the reminder text "overdue" or "redo around DD.MM.YYYY".
A worked example
Last flu vaccine on 10/15/2025, interval 12 months: due 10/15/2026, on 11/06/2026 it is "overdue" (computed by hand with the rule today ≥ due).
What this algorithm cannot do
- Intervals are general markers: your reference center sets the exact pace.
- "Never entered" does not mean the exam was not done, only that you did not enter it.
- Several modules compute the same states (prevention plan in engine.py, calendar in screening.py, vaccine log in vaccine_log.py) with the same 60-day rule written in three places.
- A future date is refused and any year before 1990 is refused at entry.
Code : src/trevor/conversation/engine.py (_prevention_rows_compute), src/trevor/lifecourse/vaccine_log.py (custom_rows), src/trevor/lifecourse/complications.py (exam_intervals, kdigo_visits_per_year), src/trevor/conversation/reminders.py (prevention_due_reminders), data/prevention.json
Insights on your crises and regularity
Over 120 days, Trévor compares the number of your crises this month and last month, your sleep before crises, the most frequent weekday, your regularity per treatment, and well- versus poorly-followed weeks. These are descriptive observations, never proof of cause.
Where you see it: Progress screen and a monthly digest sent at most once a month per person if at least one insight emerges (reminders.py:1380-1410). Crisis comparisons require the condition to have crisis prediction.
What the algorithm reads
- Start dates of journal crises (self-report)
- Minutes of sleep per night (watch / ring)
- Logged doses per treatment (self-report)
The calculation, step by step
The values used
| Name in the code | Value | Role | Where |
|---|---|---|---|
WINDOW_DAYS | 120 | Analysis window | src/trevor/conversation/insights.py:24 |
MIN_CRISES_TREND | 2 | Minimum crises for a trend | src/trevor/conversation/insights.py:25 |
MIN_CRISES_PATTERN | 4 | Minimum crises to look for a sleep pattern | src/trevor/conversation/insights.py:26 |
MIN_SLEEP_DAYS | 15 | Minimum measured nights | src/trevor/conversation/insights.py:27 |
SLEEP_GAP_MINUTES | 40 | Sleep gap deemed notable | src/trevor/conversation/insights.py:28 |
MIN_WEEKS_COMPARE | 3 | Minimum weeks in each group | src/trevor/conversation/insights.py:29 |
CRISIS_GAP_DAYS | 0.5 | Crisis-days-per-week gap deemed notable | src/trevor/conversation/insights.py:30 |
regularity thresholds | 90 and 70 | "Nice regularity" and "a reminder may help" messages | src/trevor/conversation/insights.py:137-140 |
What is shown
A list of messages with a tone (good, info, watch). Empty if nothing is reliable. The message about well-followed weeks reminds you it is a lead to confirm with your team, not a certainty.
A worked example
No numeric example was run (the calculation needs a database).
What this algorithm cannot do
- Comparing two months with 2 or more crises gives a fragile trend: one more episode flips the message from "stable" to "increasing".
- The adherence/crisis comparison does not account for the fact that you sometimes take your treatment better when you feel better.
- The 90% and 70% regularity thresholds differ from the 80% of the hydroxyurea card.
- Crisis days come only from the crisis journal.
Code : src/trevor/conversation/insights.py (compute_insights, _weekly_adherence_and_crises)
