Coach DrépanoCoach DrépanoBlood and hemolysis
Algorithm transparency

Blood and hemolysis

Hemoglobin from a photo, eye yellowness, hemolysis, transfusions and iron.

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.

Entry and conversion of lab resultsHemolysis assessment: six markers against your own baselineHemoSnap: hemoglobin estimated from a fingertip photoScleral yellow: index and estimated bilirubinReading a lab PDFHemoglobin response to transfusionsIron follow-up: ferritin, MRI and ejection fractionSymptoms after a transfusion: alert levelTransfusion history, interval and program

← All algorithms

Entry and conversion of lab results

When you type a lab value, or when Trevor reads one in a document, it converts it to the unit of its catalog, checks that it is plausible and not already in the record. These rules serve hemolysis, iron, fetal hemoglobin and creatinine. The original unit is kept next to the converted value.

Where you see it: Confirmation messages (Noted: LDH 320 U/L on 07.10.2026), refusals with a reason, and display of each result in the unit you chose in settings, with the original unit in parentheses.

What the algorithm reads

The calculation, step by step

1. Choose the unit to readIf the unit is written (with the number or separately), it isused.Otherwise, a bare number is read in the unit you chose in settings(or the catalog unit).2. Read the numberThe first number in the text is taken (comma or point decimal).No number: refusal with an example.Haptoglobin undetectable or < 0.1: value 0.Is the unit recognized?yesnoThe value is multiplied bythe unit factor (LABS tablein units.py).Refusal: unit not recognized,expecting X. A percentage isrefused for reticulocytes(the absolute count isrequired).3. Hemoglobin caseWith no unit and above 25: read as g/L, divided by 10 (112 becomes11.2 g/dL).Is the value within bounds?yesnoIt is rounded to 3 decimals.Refusal: seems outsidepossible limits, check thenumber and unit.4. Date and duplicateThe date must be in the past and less than 5 years old (10 yearsfor iron).If the same type, same day and same value already exist(difference under one millionth), nothing is added and the messagesays Already in your record.5. SaveThe value is saved to the record in the catalog unit, with theoriginal unit next to it when it differs.On display, the value is converted to the preferred unit (decimalsper unit).
Flowchart: Entry and conversion of lab results

The values used

Name in the codeValueRoleWhere
Hemoglobin3 to 20 g/dL ; g/L × 0.1Bounds and factor.src/trevor/units.py:83-84
Total bilirubin1 to 600 µmol/L ; mg/dL × 17.1Bounds (1 to 600) and factor.src/trevor/units.py:85-86
Indirect bilirubin0.5 to 600 µmol/L ; mg/dL × 17.1Bounds and factor.src/trevor/units.py:87-88
Creatinine10 to 1500 µmol/L ; mg/dL × 88.4Bounds and factor.src/trevor/units.py:89-90; src/trevor/royal/labs.py:22
Ferritin1 to 60000 µg/L ; ng/mL × 1 ; mg/L × 1000Bounds and factors.src/trevor/units.py:91-92
Haptoglobin0 to 8 g/L ; mg/dL × 0.01 ; mg/L × 0.001Bounds and factors.src/trevor/units.py:93-94
LDH30 to 6000 U/L ; UI/L × 1Bounds.src/trevor/units.py:95-96
Reticulocytes0.5 to 1500 ×10⁹/L ; /µL × 0.001 ; G/L × 1Bounds and factors.src/trevor/units.py:97-99
Hemoglobin S, fetal hemoglobin0 to 100 %Bounds in percent.src/trevor/units.py:100-101; src/trevor/royal/labs.py:21
MAX_BACK_DAYS5 years (hemolysis, HbF, creatinine); 10 years (iron)Maximum age of an entered date.src/trevor/hemolysis.py:262; src/trevor/royal/labs.py:19; src/trevor/iron.py:35
Hemoglobin without unitabove 25: divided by 10Heuristic for g/L with no unit.src/trevor/hemolysis.py:289

What is shown

For each entry: either the value saved in the catalog unit with a confirmation sentence, or a refusal that gives the reason in one sentence (no number, unit not recognized, percentage refused, out of bounds, future or too old date, record not open), or Already in your record. No level or clinical threshold here.

A worked example

2.5 mg/dL of total bilirubin becomes 2.5 × 17.1 = 42.75 µmol/L. A bare 112 of hemoglobin becomes 11.2 g/dL. 2% of reticulocytes is refused. Undetectable haptoglobin becomes 0 g/L. 95 g/L of hemoglobin becomes 9.5 g/dL (results obtained by running normalize_lab).

What this algorithm cannot do

Validation. The conversion factors (bilirubin 17.1; creatinine 88.4; haptoglobin 100 mg/dL for 1 g/L; ferritin 1 for 1) are usual laboratory conventions, flagged in units.py as rounded clinical-practice values. The plausibility bounds are the author's choices to catch typos, with no cited source. The hemoglobin g/L heuristic is a practical rule.

Code : src/trevor/hemolysis.py (LAB_ENTRY, normalize_lab, add_lab, items_from_parsed), src/trevor/iron.py (normalize, add), src/trevor/royal/labs.py (LAB_EXTRA, normalize, add_lab), src/trevor/units.py (LABS, lab_factors, entry_unit, original_reading, lab_to_unit, lab, lab_pair)

Hemolysis assessment: six markers against your own baseline

Trevor compares your latest value of each marker (total and indirect bilirubin, LDH, reticulocytes, haptoglobin, hemoglobin) with the median of your earlier values. It counts how many recent markers move in the direction of hemolysis and derives a level: stable, to watch, or suggests a hemolysis flare. It describes your data and makes no diagnosis.

Where you see it: Bot Hemolysis screen, web record page and app: one level sentence, the list of flagged markers, what is missing to conclude, and a reminder that these are not diagnoses.

What the algorithm reads

The calculation, step by step

1. Gather the seriesOne series per marker, sorted by date, without exact duplicates(same day, same value).Bilirubin in mg/dL is multiplied by 17.1.Hemoglobin also takes HemoSnap lab values; a value above 40 isread as g/L and divided by 10.At least 2 earlier values?yesnoThe median of those values iscomputed: your baseline.Status no baseline: themarker is shown but cannot beflagged.Haptoglobin: is the baselinealready 0.1 g/L or less?yesnoStatus undetectable: nocomparison, since an alreadycollapsed value cannot fallfurther.Go on to the comparison.3. Marker thresholdRising markers: flagged if last value ≥ baseline × (1 +threshold). Total and indirect bilirubin +30%, LDH +25%,reticulocytes +30%.Haptoglobin: flagged if last value ≤ baseline × 0.5 (a 50% fall).Hemoglobin: flagged if last value ≤ baseline - 1 g/dL.4 or more earlier values?yesnoThe gap must also reach 2robust standard deviations:1.4826 × median absolutedeviation, at least 8% of thebaseline.The relative threshold isenough.5. Count the panelThe most recent date across all markers is the reference.A marker is current if its last measurement is 21 days or lessbefore that reference.Current markers (whatever their status) and flagged ones arecounted.Fewer than 2current markersLevel not enoughdata: at least twoare needed amongbilirubin, LDH,reticulocytes,haptoglobin,hemoglobin.The bot says whatis missing and howto add results.2 or more currentmarkers0 flagged: stable.1 flagged: towatch. 2 or moreflagged: suggestsa hemolysis flare.Flagged markersare listed withtheir value andyour baseline.Recent transfusionIf the lasttransfusion was 0to 28 days ago andthe level is towatch or flare, awarning mentions apossible delayedreaction andadvises callingthe care teamwithout delay.
Flowchart: Hemolysis assessment: six markers against your own baseline

The values used

Name in the codeValueRoleWhere
PANEL_DAYS21Window around the most recent measurement in which markers count as one panel.src/trevor/hemolysis.py:36
MIN_BASELINE2Earlier values required.src/trevor/hemolysis.py:37
ROBUST_FROM4Earlier values from which the 2 robust SD test is added.src/trevor/hemolysis.py:38
AFTER_TRANSFUSION_DAYS28Time after transfusion that triggers the warning.src/trevor/hemolysis.py:39
MARKERS bilirubin / indirect+30 % / +30 %Relative rise thresholds.src/trevor/hemolysis.py:55-56
MARKERS LDH+25 %Relative rise threshold.src/trevor/hemolysis.py:57
MARKERS reticulocytes+30 %Relative rise threshold (absolute count).src/trevor/hemolysis.py:58
MARKERS haptoglobindrop of 50 %Relative fall threshold.src/trevor/hemolysis.py:59
MARKERS hemoglobindrop of 1.0 g/dLAbsolute fall.src/trevor/hemolysis.py:60
HAPTO_UNDETECTABLE0.1 g/LBaseline below which haptoglobin is no longer compared.src/trevor/hemolysis.py:62
Robust standard deviation floor0.08 × |baseline|The robust SD never falls below 8% of the baseline.src/trevor/hemolysis.py:95-97

What is shown

Five levels: none (no marker in the record), not enough data (fewer than 2 current markers), stable (0 flagged), to watch (1 flagged) and suggests a hemolysis flare (2 or more flagged). The to watch and flare levels invite you to talk to the care team; to watch adds that one marker alone is not enough to conclude. The delayed reaction note appears only for to watch and flare within 28 days after a transfusion.

A worked example

Total bilirubin: values 50, 55, 52, then 80 µmol/L. Baseline (median of 50, 55, 52) = 52. Threshold = 52 × 1.30 = 67.6. 80 exceeds it, and with only 3 earlier values the robust test does not apply: status flagged, +54%. With LDH also flagged within 21 days, the level becomes suggests a hemolysis flare (computed by running judge).

What this algorithm cannot do

Validation. The principle (several concordant markers, each compared with your own baseline) is presented in the module as clinical logic, with no reference cited. The thresholds (+30%, +25%, -50%, -1 g/dL), the 21-day window and the 2 robust SD rule are choices set without a study in the repository. The documentation (docs/CALIBRAGE_HEMOLYSE.md, section 5) still lists the hematology service agreement on thresholds and texts as to do.

Code : src/trevor/hemolysis.py (series, judge, lab_view, missing_markers), src/trevor/conversation/hemolysis_screens.py (_hemolysis_lines), src/trevor/transfusion.py (last_transfusion)

HemoSnap: hemoglobin estimated from a fingertip photo

HemoSnap estimates your hemoglobin from three photos of a fingertip placed over the camera lens and flash. A number appears only after calibration on one of your lab values. It is an experimental feature with no clinical validation and does not replace a blood test.

Where you see it: In the bot, My health menu, HemoSnap entry: disclaimer, intro screen, three photos, result with a range, then calibration (lab value, date, link to a transfusion). The same calculation serves the phone app (signals.py).

What the algorithm reads

The calculation, step by step

1. Read the photoThe center of the image is kept (20% to 80% of width and height),shrunk to 64 by 64 pixels.Pixels that are too dark (luminance 15 or less) or blown out (250or more) are dropped.At least 50 usable pixels are needed, otherwise the photo isunreadable.Is the photo a well-litfinger?yesnoThe optical index iscomputed: the logarithm of(median red + 1) divided by(median green + 1).The photo is rejected withadvice: unreadable, too dark(median brightness under 40),too bright (over 240), not afinger (fewer than half thepixels dominated by red) orgreen nearly gone (mediangreen under 35). You retakethe shot.2. Three shots, one indexThe bot keeps the indices of valid shots until it has 3.The index used is the median of the 3, which reduces measurementnoise.Do you already have acalibration?yesnoYour personal line turns theindex into hemoglobin (step3).No number is given. The botasks for your latest labhemoglobin to calibrate, thenits date, then the link to atransfusion.3. Fit the lineHemoglobin = slope × index + intercept.One lab point: the slope stays at the population value (9.0) andonly the intercept is adjusted.Two or more points: least squares. A zero or negative slope isreplaced by 9.0; a slope outside 3 to 18 is clamped to the bound,with the line passing through the mean of the points.Is the estimate between 4and 19 g/dL?yesnoThe number is shown, with arange (step 4).Nothing is shown or saved,and the bot asks you toretake the photo. The numberis never forced to a bound.4. Uncertainty rangeHalf-width = square root of (model squared + measurement squared).Model: 1.4 g/dL with 1 lab point, 0.8 with 2 or 3, 0.6 with 4 ormore.Measurement: 0.8 divided by the square root of the number ofshots.The displayed range is clamped between 3 and 20 g/dL.Add a calibration pointYou give the lab value (3 to20), its date (DD.MM.YYYY orToday) and the link to atransfusion.The (index, value) pair joinsthe line.Message after a transfusionIf you answer just after, atext says the value istemporarily high.With a single point, a textinvites you to add a point ata different hemoglobin level.
Flowchart: HemoSnap: hemoglobin estimated from a fingertip photo

The values used

Name in the codeValueRoleWhere
POP_SLOPE9.0Population slope (g/dL per index unit), used with a single point.src/trevor/media/hemosnap.py:30
POP_INTERCEPT6.0Population intercept. It does not enter any estimate while there is no point: with no point, no number is given.src/trevor/media/hemosnap.py:31
HB_MIN, HB_MAX3.0 and 20.0 g/dLBounds of the entered lab value and of the displayed range.src/trevor/media/hemosnap.py:32
PLAUSIBLE(4.0, 19.0) g/dLAn estimate outside this range is not shown.src/trevor/media/hemosnap.py:33
GREEN_MIN35.0Minimum median green (0 to 255): below it, the index blows up.src/trevor/media/hemosnap.py:34
SLOPE_RANGE(3.0, 18.0)Plausible personal slope.src/trevor/media/hemosnap.py:35
MODEL_BAND_1PT / 2PT / 4PT1.4 / 0.8 / 0.6 g/dLModel part of the uncertainty by number of points (1, 2 to 3, 4 or more).src/trevor/media/hemosnap.py:42-44
MEAS_BAND_1SHOT0.8 g/dLNoise of one shot, divided by the square root of the number of shots.src/trevor/media/hemosnap.py:45
HEMO_SHOTS_NEEDED3Number of consecutive shots in the bot.src/trevor/conversation/wearables_screen.py:792
Quality thresholdsbrightness 40 to 240, red share 0.5, green 35, red margin 8Conditions of quality_check; a pixel counts as red if red exceeds green and blue by more than 8.src/trevor/media/hemosnap.py:91,105-111

What is shown

The result reads: Estimate: about X g/dL, in a range of A to B g/dL, followed by the number of lab values in the calibration and a note saying the feature is experimental, not validated, and less reliable on dark skin. Internal levels: not calibrated (no number), out of range (no number), calibrated with 1, 2 or more points. Each numeric estimate is saved with its index.

A worked example

One lab point: index 0.5 for 9.0 g/dL. The slope stays 9.0 and the intercept becomes 4.5. Three shots give a median index of 0.6: 9.0 × 0.6 + 4.5 = 9.9 g/dL. Half-width: square root of (1.4² + 0.8²/3) = 1.5. Display: 9.9 g/dL, range 8.4 to 11.4 (computed by running the code).

What this algorithm cannot do

Validation. The population slope (9.0) and intercept (6.0) are described in the code as a deliberately cautious prior, with no cited source or training data: values set without a study. The band widths (1.4, 0.8, 0.6, 0.8) are the author's choices, with no published measurement. The photo quality thresholds are empirical settings (the GREEN_MIN comment cites an observed case of 22 to 30 g/dL). The log red-to-green index relies on hemoglobin absorbing green light, stated in the module without a reference. No clinical validation is established.

Code : src/trevor/media/hemosnap.py (extract_features, quality_check, fit_calibration, uncertainty_band, estimate), src/trevor/conversation/wearables_screen.py (hemosnap_process, on_hemosnap_calibrate, on_hemosnap_cal_date, on_hemosnap_cal_transf), src/trevor/storage/database.py (hemo_calibration_points), src/trevor/signals.py (_hemoglobin, hemosnap_reading)

Scleral yellow: index and estimated bilirubin

Trevor measures the yellow of the white of your eye from a photo and derives an index. After several photo and blood test pairs, it converts the index into an estimated bilirubin with an interval. Without calibration, it only says whether you are more yellow, the same, or less yellow than usual.

Where you see it: In the bot Hemolysis screen (Eye yellow line) and on the web page: saved index, estimated bilirubin with range, no clear yellow, trend, and the number of calibration pairs out of 5.

What the algorithm reads

The calculation, step by step

1. Find the white of the eyeThe photo is shrunk to at most 96 pixels and converted to CIELABcolors.A pixel counts if it is light but not blown out (lightness L from50 to under 97), not very red (a* of 8 or less) and not verysaturated (chroma of 45 or less).The largest connected area is kept.Is the photo usable?yesnoThe lighter half of the areais kept, with its median b*and its spread (interquartilerange).The photo is rejected with areason: unreadable, too small(under 24 pixels), too littlewhite (area under 2% of theimage), light too strong(over 30% of the image blownout, or over 25% blown-outpixels around the area), toodark (median lightness under58), or uneven tint (b*spread above 8).2. White sheet (optional)The center of the sheet is read (25% to 75%). It is rejected ifits median lightness is under 45 or its b* spread exceeds 6.A rejected sheet is dropped: the index is computed without it andthe message says so.3. Yellow indexWith a valid sheet: index = eye b* minus sheet b*.Without a sheet: index = eye b*.Only the index, lightness, date and sheet flag are saved; theimage is not kept.4. Calibration pairsA photo with an entered bilirubin gives an (index, bilirubin)pair.Otherwise a total bilirubin from the record within 2 days islooked up; the closest in time is used.A bilirubin in mg/dL is multiplied by 17.1.How many pairs?yesno3 or 4: provisional estimate.5 or more with a correlationof at least 0.6: calibratedestimate. In between, acorrelation under 0.3 or anegative slope gives the poorstage with no number.0 to 2 pairs: no number. Ifyou have at least 4measurements, only the trendis shown.5. Line and intervalBilirubin = slope × index + intercept (least squares).95% prediction interval: Student t × residual standard deviation ×square root of (1 + 1/n + (index - mean)²/sum of squares), with afloor of 8 µmol/L.Value and bounds are clamped between 3 and 400 µmol/L.Number displayEstimated bilirubin about Vµmol/L (between B1 and B2).If the value is under 35 andthe upper bound under 43: noclear yellow (under 35µmol/L).TrendAt least 3 measurements beforethe last one are needed.The last is compared with themedian of the previous ones:threshold = the larger of 1.5and 2 median absolutedeviations (× 1.4826).Higher, same or lower.
Flowchart: Scleral yellow: index and estimated bilirubin

The values used

Name in the codeValueRoleWhere
MIN_POINTS_PROVISIONAL3Minimum pairs for a provisional number.src/trevor/media/scleral.py:37
MIN_POINTS_CALIBRATED5Pairs for a calibrated number.src/trevor/media/scleral.py:38
MIN_R_CALIBRATED0.6Minimum correlation for calibrated (below 0.3 or negative slope: poor).src/trevor/media/scleral.py:39,218-224
BAND_FLOOR8.0 µmol/LMinimum half-width of the interval.src/trevor/media/scleral.py:40
VISIBLE_ICTERUS35.0 µmol/LBelow this bilirubin, the sclera stays white to the eye.src/trevor/media/scleral.py:41
BILI_MIN, BILI_MAX3.0 and 400.0 µmol/LDisplay and entry bounds.src/trevor/media/scleral.py:42
MG_DL_TO_UMOL17.1mg/dL to µmol/L conversion.src/trevor/media/scleral.py:43
TREND_MIN_SHIFT, TREND_MIN_HISTORY1.5 b* units; 3 measurementsMinimum shift and minimum history for the trend.src/trevor/media/scleral.py:44-45
MIN_SCLERA_FRACTION0.02Minimum share of the image occupied by the white of the eye.src/trevor/media/scleral.py:46
EYE_MIN_L, BLOWN_L, EYE_MIN_LIGHT50 ; 97 ; 58Minimum pixel lightness, blown-out threshold, minimum median lightness.src/trevor/media/scleral.py:47-49
MAX_BLOWN_IMAGE, MAX_GLARE, MAX_SPREAD0.30 ; 0.25 ; 8.0Share of blown-out image, share of glare around the white, maximum b* spread.src/trevor/media/scleral.py:50-52
EYE_PAIR_DAYS2Maximum gap between the photo and a record bilirubin.src/trevor/hemolysis.py:41

What is shown

Three calibration levels: none (fewer than 3 pairs, trend only), provisional (3 or 4 pairs), calibrated (5 or more pairs, correlation of at least 0.6), plus the poor stage (negative slope or correlation under 0.3, no number). The number is the estimated bilirubin rounded in µmol/L with its bounds. Without an estimate, the trend says more yellow, same as usual, or less yellow. A text reminds that the photo estimates only bilirubin and makes no diagnosis.

A worked example

Five (index, bilirubin) pairs: (1, 20), (2, 30), (3, 41), (4, 52), (5, 60). Slope 10.2, intercept 10, correlation 0.999: calibrated stage. New index 3.5: 10.2 × 3.5 + 10 = 45.7, i.e. 46 µmol/L. The computed interval is about 3.4, so the floor of 8 applies: 38 to 54 µmol/L. Index 0: 10 µmol/L, under 35, so no clear yellow (run with the code).

What this algorithm cannot do

Validation. The CIELAB D65 b* computation and the 95% Student table are standard methods. The 34 to 35 µmol/L (2 mg/dL) threshold for visible jaundice is given in the module documentation as the reference clinical sign, without a citation. The image thresholds (lightness, shares, spreads) and the 3 and 5 pair stages are the author's settings, with no published study in the repository. No accuracy validation is established.

Code : src/trevor/media/scleral.py (extract_eye, extract_paper, index_of, fit, estimate, trend, to_umol), src/trevor/hemolysis.py (record_eye, save_eye, calibration_points, eye_view, set_lab), src/trevor/conversation/hemolysis_screens.py (_hemolysis_lines), src/trevor/signals.py (eye_numbers)

Reading a lab PDF

Trevor reads the words and their positions in a lab PDF that contains text, finds the hemolysis markers and attaches each value to its sampling date. Nothing is invented: a value is kept only if it is aligned under a date. You review the result before anything is saved.

Where you see it: Review screen after sending the PDF: one checkbox per value read, which you tick or untick before saving.

What the algorithm reads

The calculation, step by step

1. Read the wordsEach page gives its words with coordinates.More than 300 pages: clear refusal, nothing is silently ignored.Scanned or text-less PDF: empty list, and Trevor falls back toassistant reading.2. Form linesWords whose vertical center differs by 3 points or less form aline, sorted left to right.Does the page have datedcolumns?yesnoTable mode: a header linewith at least 2 dates makingup at least 80% of the wordsin the line. Each value goesto the date whose center isnearest, within 45% of thecolumn spacing (14 pointsminimum). A missing header istaken from the previous page.Line mode: the current resulttakes the sampling date fromthe header (Ent.Dat.,Prélèvement, Abnahme), theearlier result the datewritten next to it.3. Recognize the markerThe label, with no accents or capitals, must exactly match apattern: hemoglobin, hemoglobin S, total bilirubin, indirectbilirubin, LDH, reticulocytes (absolute count), haptoglobin.Reticulocytes in o/oo or %: ignored. Hemoglobin S outside %:ignored.Neighboring tests (free hemoglobin, glycated...) match no patternand are ignored.4. Skip referencesReference ranges (7 - 25, 135-172, < 3.0) are never taken as avalue.Values left of the first column are ignored.5. Normalize and deduplicateEach value goes through catalog normalization (units, bounds). Arefused value disappears.For one marker and one date, the last one read replaces theearlier ones.Future dates are dropped. The result is sorted by date thenmarker.6. ReviewAt most 60 values, most recent kept first, go to the reviewscreen.A < sign is kept as a below-limit flag, but the value read staysthe number written.
Flowchart: Reading a lab PDF

The values used

Name in the codeValueRoleWhere
MAX_PAGES300Beyond it, refusal.src/trevor/media/lab_pdf.py:36
Row tolerance3.0 pointsMaximum vertical gap within a line.src/trevor/media/lab_pdf.py:121
Column tolerancemax(14, 0.45 × minimum gap between columns)Maximum distance between a value and its column.src/trevor/media/lab_pdf.py:166-167
Share of header datesat least 2 dates and 80 % of the rowCondition for a header line.src/trevor/media/lab_pdf.py:155
Column deduplication8 pointsTwo dates within 8 points of center form one column.src/trevor/media/lab_pdf.py:158
Value cap60Values kept on return.src/trevor/hemolysis.py:365

What is shown

A list of values (marker, value in the catalog unit, unit, date, below-limit flag, original unit) to review. No clinical level or threshold.

A worked example

none (reading depends on the layout of a real PDF, which is not in the repository). Only label recognition was run: Total bilirubin in µmol/l becomes bilirubin; Reticulocytes nb.abs. in o/oo and Free hemoglobin give nothing.

What this algorithm cannot do

Validation. Coordinate-based reading method written for the HUG and German-language reports cited in the module, with no public test set seen in the repository here. The tolerances (3 points, 45%, 80%, 8 points) are empirical settings. Review by the person is the intended safeguard.

Code : src/trevor/media/lab_pdf.py (read_lab_pdf, marker_of, _read_table, _read_inline, _columns, _split), src/trevor/hemolysis.py (read_pdf_items, normalize_lab)

Hemoglobin response to transfusions

Trevor compares your hemoglobin before and after each transfusion to estimate what one unit gives you, how long the gain lasts, and whether exchanges respect the dose and interval of their protocol. It describes your data and recommends no dose.

Where you see it: Your transfusion response card: dashboard web page, app and export.

What the algorithm reads

The calculation, step by step

1. Hemoglobin measurementsLab: measurement error 0.25 g/dL. HemoSnap estimate: 1.0 g/dL.If a HemoSnap reading has a lab value, that value is used, not theestimate.A value above 30 is read as g/L and divided by 10.2. Before and after pairsOne pair per session with at least 1 unit.Before: the closest measurement in the 7 days before (same dayincluded).After: the closest measurement on days 1 to 7 after.With no measurement on one side, no pair.3. Jump per unitOn simple transfusions only: delta = after - before, modeled asdelta = b × units (line through the origin, weighted by1/variance).Standard error = the larger of the measurement error and theobserved spread (spread computed from 3 pairs).4. Pull toward a referenceVariance-weighted average between your slope and an adultreference of 1.0 g/dL per unit (SD 0.4).With few pairs the reference dominates; with many, your slopedominates.Fewer than 3 pairs: provisional; 3 or more: enough to show apersonal number.5. Duration of the gainFor the last simple transfusion with units and a pair:measurements on days 1 to 60 whose excess over the before-valueexceeds 0.3 g/dL are taken.Regression of log(excess) on days: half-life is ln 2 divided bythe decay rate.At least 2 measurements and a positive decay are needed, otherwisenothing.DoseUnit target:automated exchange6 to 9 for 77 kg,manual exchange 3to 5, scaled toweight when known.Status: below ifunits + 0.5 isunder the minimum,above if units -0.5 exceeds themaximum, otherwisein range.IntervalTarget: automatedexchange 5 to 6weeks, manual 2 to3 weeks.Median of theintervals betweenexchanges; shortbelow minimum -0.5, long abovemaximum + 0.5,otherwise inrange.Hemoglobin SLatest assayagainst the 30%reference.Slope inpercentage pointsper unit onexchanges with anassay in thewindows.
Flowchart: Hemoglobin response to transfusions

The values used

Name in the codeValueRoleWhere
PRIOR_PER_UNIT1.0 g/dL per unitAdult reference.src/trevor/transfusion_response.py:35
PRIOR_SD0.4Uncertainty of the reference.src/trevor/transfusion_response.py:36
LAB_SD, ESTIMATE_SD0.25 ; 1.0 g/dLMeasurement error of a lab and of a HemoSnap estimate.src/trevor/transfusion_response.py:37
HBS_SD2.0 percentage pointsError of a hemoglobin S assay.src/trevor/transfusion_response.py:38
HBS_TARGET30.0 %Reference under which hemoglobin S is sought to be kept between exchanges; the code says to confirm with hematology.src/trevor/transfusion_response.py:39
PRE_WINDOW, POST_WINDOW7 days before; days 1 to 7 afterSearch windows.src/trevor/transfusion_response.py:40-41
MIN_PAIRS_INDIVIDUAL3Pairs for a non-provisional personal number.src/trevor/transfusion_response.py:42
RECENT_EXCHANGES6Exchanges considered for the share on target.src/trevor/transfusion_response.py:43
PROTOCOLS exchangeunits 6 to 9 (for 77 kg), weeks 5 to 6Targets of automated exchange.src/trevor/transfusion_response.py:49
PROTOCOLS exchange_manualunits 3 to 5 (for 77 kg), weeks 2 to 3Targets of manual exchange.src/trevor/transfusion_response.py:50
Half-lifedays 1 to 60, excess > 0.3 g/dL, decay > 0.0001 per dayCalculation conditions.src/trevor/transfusion_response.py:216-225

What is shown

Gain per unit in g/dL with its standard error, the raw slope, the number of pairs and the provisional flag; half-life in days; for each exchange type: last number of units and status (below, in range, above), target in units, share of the last 6 exchanges on target, median interval and its status (short, in range, long), units per kg; hemoglobin S: latest assay, 30% reference, slope per unit. Personal numbers are declared enough only with 3 pairs of simple transfusions.

A worked example

Three simple transfusions: 2 units (7.0 then 9.0 g/dL), 2 units (7.4 then 9.1), 3 units (6.8 then 10.1). Raw slope 1.018 g/dL per unit, standard error 0.086. After pulling toward 1.0 (SD 0.4): 1.017 with error 0.084. Automated exchange for 70 kg: target 5.5 to 8.2 units; 6 units is in range; median interval of 7 weeks: long (above 6.5); 6.5 weeks: in range (run with the code).

What this algorithm cannot do

Validation. The adult reference of 1 g/dL per unit and its SD of 0.4 are set in the code without a reference. The measurement errors of the lab (0.25), the estimate (1.0) and hemoglobin S (2.0) are the author's choices. The targets by modality are starting values to be confirmed by hematology. The documentation lists validation on a cohort with hematology agreement as a future step. The statistical method (weighted least squares, Bayesian shrinkage) is standard.

Code : src/trevor/transfusion_response.py (hb_readings, make_pairs, slope_through_origin, shrink, half_life_days, units_target, dose_status, interval_status, build), src/trevor/transfusion.py (payload, _response), src/trevor/dashboard/data.py

Iron follow-up: ferritin, MRI and ejection fraction

Trevor records the iron measurements your care team gives you (ferritin, heart and liver T2*, liver iron, ejection fraction), plots them and reads them against reference points from the Thalassaemia International Federation. Each entry resets the reminder for the annual exam. It gives no treatment instruction.

Where you see it: Iron follow-up screen and page for the relevant conditions (thalassemia): one line per measurement type with the latest value, its date, a reference sentence, the direction since the previous measurement and a small chart.

What the algorithm reads

The calculation, step by step

1. EnterThe number is read with an optional unit (ferritin: µg/L, ng/mL ormg/L; T2*: ms; LIC: mg/g; ejection fraction: %).Ferritin accepted from 1 to 60,000; heart T2* from 1 to 80; liverT2* from 0.1 to 80; LIC from 0.1 to 80; ejection fraction from 10to 90.Date past and under 10 years old. Same type, same day, same value:already noted.2. Exam reminderThe reminder date (ferritin every 3 months, annual MRI) restartsfrom the most recent measurement.3. One value per dayFor each type, only one value per day is kept (the last recordedthat day), sorted by date.Ferritin2,500 µg/L ormore: highreference, contactthe care team.1,000 to under2,500: closermonitoring.Under 1,000: belowthe reference.Heart T2*Under 10 ms: highheart riskreference.10 to under 20 ms:value consideredabnormal.20 ms or more:above theabnormalityreference.Liver iron (LIC)15 mg/g or more:severe overload.7 to under 15:moderate overload.Under 7: below thereference.4. Direction and chartDirection: the last two values are compared; a gap under 10%:stable, otherwise up or down. A single measurement: nothing.Chart: at most the last 8 values, mapped to 8 bar heights betweenminimum and maximum; all values equal: mid-height bars.
Flowchart: Iron follow-up: ferritin, MRI and ejection fraction

The values used

Name in the codeValueRoleWhere
FERRITIN_THAL_WATCH1000.0 µg/LFerritin to watch.src/trevor/value_alerts.py:48
FERRITIN_THAL_REPORT2500.0 µg/LFerritin to report.src/trevor/value_alerts.py:49
CARDIAC_T2_ABNORMAL_MS20.0 msHeart T2* abnormal below.src/trevor/iron.py:30
CARDIAC_T2_HIGH_RISK_MS10.0 msHeart T2* high risk below.src/trevor/iron.py:31
LIC_MODERATE, LIC_SEVERE7.0 ; 15.0 mg/gModerate and severe overload thresholds.src/trevor/iron.py:32-33
FLAT_PERCENT10.0Gap under which two measurements read stable.src/trevor/iron.py:34
MAX_BACK_DAYS3650Maximum age of an entered date.src/trevor/iron.py:35

What is shown

Per measurement type: latest value, date, reference sentence, direction and chart. Ferritin: 3 levels (under 1,000, 1,000 to 2,499, 2,500 or more). Heart T2*: 3 levels (20 or more, 10 to under 20, under 10). LIC: 3 levels (under 7, 7 to under 15, 15 or more). Liver T2* and ejection fraction: no reference, value only recorded and charted. The sentences invite you to talk with the care team.

A worked example

Ferritin 1,450 µg/L: level to watch (between 1,000 and 2,500). Last two ferritins 1,000 then 1,080: gap 8%, under 10%, so stable; 1,000 then 1,200: 20%, so up. Heart T2* 15 ms: abnormal. Chart of 1, 2, 3, 5: ▁▃▅█ (run with the code).

What this algorithm cannot do

Validation. The reference points come from the TIF 2021 (Thalassaemia International Federation), according to the module: heart T2* 10 and 20 ms, LIC 7 and 15 mg/g, ferritin 1,000 and 2,500 µg/L (chapter 3 for ferritin per value_alerts.py). The code states they are to be validated by a hematologist (docs/THALASSEMIE_AUDIT.md, sections 5 and 8, not read). The 10% threshold for stable is the author's choice.

Code : src/trevor/iron.py (normalize, reading, trend, spark, series, build, add), src/trevor/value_alerts.py (FERRITIN_THAL_WATCH, FERRITIN_THAL_REPORT), src/trevor/conversation/iron_screens.py, src/trevor/dashboard/phone_care_routes.py

Symptoms after a transfusion: alert level

You tick the symptoms you have felt since a transfusion. The server gives the declaration the level of the most serious symptom ticked: warning sign, to report, or noted. It then tells you whom to call, and flags a possible delayed reaction if the sign and timing fit.

Where you see it: After my transfusion screen: symptom list, advice text by level, declaration history. The server computes the level; the client does not choose it.

What the algorithm reads

The calculation, step by step

1. FilterOnly catalog symptoms are kept, in catalog order, withoutduplicates.No symptom, or an unreadable, future or over-730-day date: thedeclaration is refused.2. LevelLevel = the highest of the ticked symptoms (noted < to report <urgent).Urgent: fever, difficulty breathing, chest pain, face swelling,faintness or confusion, dark urine.To report: chills, hives, back pain, unusual pain, yellow skin oreyes, fatigue or pallor, swollen legs, very fast heartbeat.Noted: headache, nausea, other.3. Link to a transfusionThe linked transfusion is the most recent known on the day of thesymptoms or before.Delay = days between the transfusion and symptom onset.Delayed reaction possible?yesnoYes if the delay is 1 to 28days and one of these signsis ticked: dark urine, yellowskin or eyes, fatigue,unusual pain, fever, backpain.No delayed reaction mention.4. AdviceUrgent: call your country's emergency number, say you weretransfused and give the date.To report: call your care team today, even if the symptoms seemmild; if you get worse, the emergency number.Noted: talk about it at the next contact, sooner if it lasts.For urgent and to report, the delayed reaction text is added if itis possible.
Flowchart: Symptoms after a transfusion: alert level

The values used

Name in the codeValueRoleWhere
DELAYED_HINTSdark_urine, jaundice, fatigue, pain, fever, back_painSigns suggesting delayed destruction of transfused red cells.src/trevor/transfusion.py:62
DELAYED_WINDOW_DAYS(1, 28)Window from the next day to four weeks.src/trevor/transfusion.py:63
RECENT_DAYS30An older declaration no longer appears in the record banner.src/trevor/transfusion.py:64
MAX_NOTE280Note characters.src/trevor/transfusion.py:66
INTENSITIES0, 1, 2, 3Unknown, mild, bothersome, very strong.src/trevor/transfusion.py:68
SYMPTOMS6 urgent, 8 to report, 3 notedCatalog and levels.src/trevor/transfusion.py:40-57

What is shown

Three levels: warning sign (urgent), to report, noted. The urgent text tells you to call your country's emergency number. The delayed reaction note is added for urgent and to report when the delay is 1 to 28 days with a suggestive sign.

A worked example

Symptoms ticked: fever and nausea. Fever is urgent, nausea noted: the level is urgent. Tick only hives: to report. Yellow skin or eyes 10 days after the transfusion: delayed reaction possible; the same sign on the same day (delay 0): no (run with the code).

What this algorithm cannot do

Validation. The module claims to reuse the classic signs of a transfusion reaction and the HUG hematology protocol rule for fever. The catalog, the level classification and the 1 to 28 day window are the author's choices, not checked here against that protocol. Review by hematology is announced as to do (docs/conformite/AIPD_TREVOR.md, cited by the module, not read).

Code : src/trevor/transfusion.py (SYMPTOMS, tier_of, delayed_possible, linked_transfusion, record_reaction, advice_lines)

Transfusion history, interval and program

Trevor finds your last transfusion from the several places where you may have noted it, counts the days elapsed, computes the mean interval between transfusions and, if you have a regular program, says whether you are late or on time. It also records the number of units and the length of each session.

Where you see it: Bot My transfusion program screen (status, days and weeks elapsed, number of transfusions, mean interval, delay or next date), then questions about the number of units and the duration. Same data on the web page and in the app (transfusion.payload).

What the algorithm reads

The calculation, step by step

1. Record the sessionYou press I was transfused today or write a date.The date must be today or past, and less than 730 days (two years)old.Type: transfusion, automated exchange or manual exchange.2. Units and durationUnits: buttons 1 to 6 or a written number; between 1 and 30,otherwise 0 (unknown).Duration: buttons from 1 to 8 hours or a written duration up to 24hours; outside 1 to 1440 minutes, 0 (unknown).Noting the same date again completes the session already recorded.3. Last transfusionThree sources are read: the session history, the profile date andthe Last transfusion line on the card.Future dates are dropped; the most recent wins.Elapsed days count from Geneva time in the service, from theperson's local time in the bot screen.4. History and intervalDates are deduplicated; the gap in days with the previoustransfusion is computed for each.Mean interval = mean of the gaps between distinct dates in thehistory, in weeks (1 decimal).The number of transfusions is the number of distinct dates.Regular program active?yesnoProgram interval (at least 1week, 4 by default). Delay =weeks elapsed minus interval;from 0.5 week, the bot showsthe delay.No delay message and noplanned date.5. Next dateUnder 0.5 week of delay, the bot shows the next date: lasttransfusion + interval in weeks, and the weeks remaining (0 atminimum).
Flowchart: Transfusion history, interval and program

The values used

Name in the codeValueRoleWhere
MAX_BACK_DAYS730Maximum age of a recorded transfusion.src/trevor/transfusion.py:65
MAX_UNITS30Maximum units per session.src/trevor/transfusion.py:67
MAX_MINUTES1440Maximum session length.src/trevor/transfusion.py:111
DURATION_CHOICES_HOURS1 to 8 hoursDuration buttons.src/trevor/conversation/carnet_screens.py:26
Delay threshold0.5 weekFrom this delay, the bot shows a delay message.src/trevor/conversation/wearables_screen.py:644-650
Default interval4 weeksUsed if the program interval is empty or zero in the bot screen.src/trevor/conversation/wearables_screen.py:644
Unit buttons1 to 6, number typed up to 30Choices offered.src/trevor/conversation/wearables_screen.py:701-712

What is shown

For each transfusion: date, type, units, duration, date source (history, profile or card) and days since the previous one. Overall: date of the last, days and weeks elapsed, count, mean interval in weeks, regularity (standard deviation of the intervals in days, computed but not shown in the bot screen). With a program: running X weeks late (planned interval Y) or next transfusion on DD.MM.YYYY in Z weeks. No clinical threshold.

A worked example

none (the gap calculation reads directly in the code; no data set was run). Code reading: 6-week program, last transfusion 45 days ago, i.e. 6.4 weeks; delay = 6.4 - 6 = 0.4 week, under 0.5, so the bot shows the next date.

What this algorithm cannot do

Validation. These are counting and entry rules, with no statistical model. The caps (30 units, 24 hours, 730 days) and the 0.5-week threshold are the author's choices to catch typos, with no cited source.

Code : src/trevor/transfusion.py (transfusion_dates, last_transfusion, history, record_transfusion, clean_units, clean_minutes), src/trevor/conversation/wearables_screen.py (screen_transfusion, on_transfusion_units), src/trevor/conversation/carnet_screens.py (screen_transfusion_minutes, on_transfusion_minutes), src/trevor/storage/database.py (transfusion_stats)

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.