~/blood-test-results-tracker redact local AI heic → jpg ☕ Support me apps ← about me

Blood Test Results Tracker — compare your lab results over time

Drop in the lab reports your doctor sends you — PDF, scan or phone photo. The text layer or OCR reads every test, value, unit and reference range, then you see each marker as a trend against the lab's own limits, a heatmap of every report, and a side-by-side comparison of any two dates. An optional AI model runs inside your browser to read lines OCR missed and to explain the trends in plain English. Not medical advice — discuss results with your doctor.

Guide — from a pile of lab PDFs to one timeline

Five steps, a few minutes per report. Everything happens in this browser tab.
1Drop in a report

Drag the PDF your doctor or lab sent you onto the drop zone, or a scan or phone photo. Several files at once is fine; Ctrl+V pastes a screenshot.

2Let it read the values

A PDF with a text layer is read directly. A scan or photo goes through OCR in your browser — pick the report's language first for the best result.

3Check every row

Hover a row and the line lights up on the page. Yellow rows were corrected or guessed. Fix the date, untick what you don't want, then save.

4Compare over time

The Dashboard shows every test against its range, Trends one test over the years, the Heatmap all reports at once and Compare two dates side by side.

5Keep a backup

Results stay on this device. Download a JSON backup now and then, or a CSV for a spreadsheet. Optional: let the in-browser AI explain the trends.

What each view answers

NOWDashboard
question
What is out of range in my latest report, and what moved?
filters
category · out of range now · near a limit · ever out of range · moved a lot
TRENDTrends + overlay
question
Is my LDL, TSH or ferritin slowly creeping towards a limit?
compare with
previous · first · average · any earlier date
overlay
up to four tests on one 0–100% range scale
PATTERNHeatmap
question
Which tests keep coming back high or low over the years?
colour by
position in the range, or change since the previous report
DIFFCompare reports
question
What changed between this check-up and the last one — or the one before my treatment?
sorted by
category · biggest change · status changes

How to track and compare blood test results over time

A single blood test is a snapshot. What usually matters more is the direction: a cholesterol value that climbs a little every year, a ferritin that keeps sliding, a TSH that drifts towards the upper limit. Labs print every report on its own, often with slightly different reference ranges or units, so comparing them by hand means flipping between PDFs. This tracker puts every report on one timeline. For each test it keeps the value, the unit, the flag and the reference range printed by the lab on that date, and draws them together.

How do I read a reference range?

The range next to each result (for example 4,35 – 5,61, ≤ 190 or ≥ 40) is the interval in which most healthy people of a reference group fall. For most tests it is the middle 95% of that group, which means that 1 healthy person in 20 lands outside it on any one test. With 20 tests that were unrelated, the chance that at least one is flagged would be 1 − 0.9520 ≈ 64%. That is why a single value just outside the range is often not a problem on its own, and why the trend and the doctor's view of the whole picture matter.

Some ranges have only one side. A ≤ limit (LDL cholesterol, CRP, liver enzymes) is an upper limit; a ≥ limit (HDL cholesterol, vitamin B12, eGFR) is a lower limit. Lipid limits are often targets that depend on your cardiovascular risk rather than statistical ranges — the report may list several, and the tracker keeps the one on the result line.

Why every report keeps its own range

Ranges differ between labs, analysers and sometimes by age and sex, and a lab can change its method. A value of 4,21 mU/L TSH is "high" against 0,27 – 4,20 and normal against a range ending at 4,50. So the trend chart draws the range as a band that steps with each report, and every status (in range, near a limit, high, low) is judged against the range printed on that day. If a result has no range, the tracker borrows the most recent one for the same test and marks it with an asterisk.

Statuses and colours

StatusRule used by the tracker
in rangeinside the lab range and not in its outer 10%
near limitinside the range but within 10% of its width from a limit (for a one-sided range: above 90% of a ≤ limit, or below 110% of a ≥ limit)
high / lowoutside the range
far high / far lowoutside the range by more than 25% of its width

Changes are coloured by whether the value moved further inside the range (green) or towards or past a limit (orange) — not by whether the number went up or down, because up is good for HDL and bad for LDL.

Comparing two results: when is a change real?

Two measurements of the same healthy person are never identical. Part of the difference is the lab (analytical variation), part is the body itself from day to day (within-person biological variation). Clinical chemists combine them in the reference change value: RCV = √2 × 1.96 × √(CVa² + CVi²). For a test with 3% analytical and 5% biological variation that is √2 × 1.96 × √(9 + 25) ≈ 16%: smaller changes between two results can be noise. The tracker shows every change as an absolute value, a percentage and a share of the reference range width, and the Trends tab fits a straight line through all values (with R²) so one noisy result weighs less than a steady drift. The trend card only mentions when a limit would be reached if the fit is reasonable (R² ≥ 0.5) and the limit is less than ten years away — it is a description of the past, not a prediction.

Things that change a result without anything being wrong

Units: mg/dL, mmol/L and the rest

When reports use different units, the tracker converts them so they land on one chart. Mass ↔ molar conversions use the molar mass of the substance; counts are converted by their power of ten. Some examples, as calculated by this page:

TestConventionalSIFactor
Glucose100 mg/dL5.55 mmol/L÷ 18.016
Cholesterol (total, LDL, HDL)200 mg/dL5.17 mmol/L÷ 38.67
Triglycerides150 mg/dL1.69 mmol/L÷ 88.57
Creatinine0.86 mg/dL76 µmol/L× 88.4
Vitamin D (25-OH)30 ng/mL74.9 nmol/L× 2.496
Vitamin B12301 ng/L222 pmol/L÷ 1.355
HbA1c5.7 %39 mmol/mol(% − 2.15) × 10.929
White blood cells5,040 /µL5.04 × 10⁹/L÷ 1,000

Pick the unit per test in the Trends tab. Lipoprotein(a) is an exception: mg/dL and nmol/L measure different things and cannot be converted, so values in another unit are hidden from that chart and the page says so.

How the OCR and parser work

For a PDF with a text layer, pdf.js returns every word with its position; the tracker rebuilds the lines. A scanned page, a photo or a PDF without text goes through Tesseract OCR (tesseract.js 4.1.1) at roughly 300 dpi. Each line is then split into test name, flag (+, H, L, *), value (with < or >), unit and range, and the name is matched against a dictionary of about 80 common tests in Dutch, French, German and English — Erytrocyten, Leukocyten, IJzer, Foliumzuur, Urinezuur, Natrium, Kalium, Gamma-GT and so on. Neutrophils and the other white-cell types appear twice on most reports; the unit (% or a count) decides which one a line is.

OCR makes typical mistakes, and the parser repairs the common ones and marks them in yellow: ≥197 read as 2197, ≤ read as s or =, % read as 4, /µL read as /L, mmol read as mmo1, and a lost decimal comma (49 for 4,9) when the lab flags out-of-range values and this one was not flagged. Lines that look like results but match no known test can be added by hand or read by the AI. Reports with history columns (several dates side by side) are split into one report per date.

Tip for photos: shoot straight from above in good light, fill the frame with the table, and avoid shadows. A PDF from your lab or the patient portal is always better than a photo of a printout. HEIC photos from an iPhone can be converted first with the HEIC to JPG converter.

The optional AI: what it does and doesn't do

The AI explain tab loads a small open model (Qwen 2.5, Llama 3.2 or Phi 3.5) with WebLLM. It runs on your graphics card through WebGPU, so it needs a recent Chrome or Edge on a computer (or another browser with WebGPU), and downloads 0.9–2 GB once. It gets a compact table of your numbers — test, value, unit, lab range, status, previous values and trend — never your name or the scanned page. It can read lines the parser missed (it returns JSON that lands in the review table, marked yellow) and write a plain-language overview with questions to ask your doctor. Small models do make mistakes; the page never lets the AI change saved values on its own. copy as prompt gives you the same text to paste into another assistant — but then your numbers leave this device, which is your choice.

Privacy: where your data goes

Your reports are read in the browser and saved in this browser's IndexedDB; nothing is uploaded and there is no account. The page downloads only code and data files: pdf.js from cdnjs, tesseract.js from cdnjs and jsDelivr, the OCR language files from tessdata.projectnaptha.com, and — only if you load the AI — the WebLLM runtime from jsDelivr and the model weights from Hugging Face. Privacy-friendly Simple Analytics counts page views without cookies. Clearing your browser's site data deletes your results, so keep the JSON backup somewhere safe. Want to share a report with someone? Black out your name and national number first with the PII redaction tool — it runs in the browser too.

Not medical advice

This is a tool to organise and visualise your own results. It does not diagnose anything, and "in range" does not mean healthy any more than "high" means ill. Discuss your results — and any trend you notice here — with your GP or specialist.

More private tools

Ask questions about documents with the free browser-based AI, black out personal data with the PII redaction tool, or browse all free tools.

Reading…