Skip to content

How to Build a Blood Test Tracker You Own

Oak
A split glowing log in a dark forest with hundreds of colored sap channels forming rows of bright nodes.

Consumer apps such as Carrot Care and InsideTracker work perfectly well as viewers, as do the various “upload your PDF and get AI insights” sites. Some of them produce decent charts. What they hide are the two decisions that determine whether a trend is real. The first is how you reconcile results across labs and methods. The second is what size of change is large enough to distinguish signal from the combination of analytical and biological noise. What they hide are the two decisions that determine whether a trend is real: how you reconcile results across labs and methods, and what size of change is large enough to distinguish signal from the combination of analytical and biological noise. Those decisions belong in code you can read.

This page walks through the schema and the normalization steps. It then covers the statistics and the specific ways that tracking goes wrong. Interpretation of any individual result is a clinical matter, and we say so where it applies.

The schema: long format, one row per analyte per draw

The first design choice is the shape of the table, and it has consequences for everything downstream. Wide tables, with one column per analyte, look convenient until the first time a lab reports a new panel or switches to a different unit. At that point the schema has to change. Long format avoids that problem entirely:

CREATE TABLE results (
  draw_id       TEXT NOT NULL,      -- one per venipuncture
  collected_at  TEXT NOT NULL,      -- ISO 8601 with timezone
  loinc         TEXT,               -- e.g. '4548-4' HbA1c
  analyte       TEXT NOT NULL,      -- canonical local name
  value_num     REAL,
  value_text    TEXT,               -- '<0.01', 'NEGATIVE'
  unit          TEXT NOT NULL,      -- canonical, post-conversion
  unit_raw      TEXT,
  ref_low       REAL,
  ref_high      REAL,
  lab           TEXT NOT NULL,      -- Quest, Labcorp, hospital
  method        TEXT,               -- assay/platform if reported
  fasting_hrs   REAL,
  notes         TEXT,
  PRIMARY KEY (draw_id, loinc, analyte)
);

Two details in that schema are worth dwelling on. Keep value_text separate from value_num, because censored results such as “<0.01 for high-sensitivity CRP” or “<3 for hs-troponin” are not zeros, and silently coercing them to 0 will distort any slope you fit afterwards. Keep unit_raw forever as well, so that a conversion bug remains auditable long after it was introduced.

The genuinely hard part is analyte identity. “Vitamin D” can mean 25-hydroxyvitamin D total, D2, or D3. LDL can be either calculated by Friedewald or Martin-Hopkins or directly measured, and the two diverge at low triglycerides and in non-fasting samples. The remedy is to map every analyte to a LOINC code at ingestion, treating unmapped rows as a queue to resolve by hand. LOINC is the standard vocabulary for laboratory observations. The codes you will see most often include 4548-4 for HbA1c, 2345-7 for serum glucose, and 2160-0 for creatinine. Others are 1742-6 for ALT, 3016-3 for TSH, and 2276-4 for ferritin. A dictionary of roughly 80 codes covers almost everything a general panel will throw at you.

Unit conversion is mechanical but unforgiving, so it belongs in the loader rather than in your head. Glucose in mg/dL divided by 18.0182 gives mmol/L, creatinine in mg/dL times 88.42 gives µmol/L, and cholesterol in mg/dL divided by 38.67 gives mmol/L. Pick one canonical unit per LOINC code and assert it as the data comes in. The reason to be strict is that the cost of a mistake varies wildly: a unit flip on ferritin (ng/mL versus µg/L, which are identical) is harmless, whereas a silent flip on creatinine is a factor of 88.

Getting the data in

Results can reach your pipeline by three routes, and they differ enough in reliability that it is worth preferring them in order.

FHIR JSON is the best input when you can get it. FHIR is the interoperability standard that health systems use to exchange records, and if you have connected health records in Apple Health, the export archive contains a clinical-records/ directory of FHIR resources. Each Observation entry carries code.coding with the LOINC code, along with valueQuantity.value, valueQuantity.unit, effectiveDateTime, and referenceRange. Parsing that takes a few lines of json and pandas.json_normalize, and it eliminates OCR entirely. Many US portals also expose a patient API under the 21st Century Cures Act rules, and self-hosted aggregators such as Fasten Health can pull from them.

CSV exports from consumer testing companies are the second choice. They are usually clean, but they often omit the assay method and the specimen collection time, and some round values to fewer digits than the lab originally reported.

PDFs are the last resort. Start with pdftotext -layout, since layout-preserving text combined with a per-lab regex is more reliable than a general model working on the same document. For scanned pages, run ocrmypdf --force-ocr first and then use the same pipeline. If you do use a language model for extraction, force structured output with a JSON schema and validate every field mechanically afterwards. The unit must be in the allowed set for that LOINC code. The value must fall inside a wide plausibility band, such as serum sodium in 100 to 180 mmol/L. The date must lie within your plausible history. In our experience, model extraction errors cluster on multi-column panels and on flagged values where the reference range column bleeds into the result column, so the validator earns its keep.

The statistics: reference change value, not the reference interval

Once the data is in, the question becomes how to read it, and this is where most tools quietly mislead. The population reference interval answers “is this value unusual among adults?” Tracking asks something different: “has my value changed?” The tool for the second question is the reference change value, which combines analytical imprecision (CVa, the coefficient of variation of the assay) with within-subject biological variation (CVi, how much you fluctuate around your own set point):

RCV = sqrt(2) * Z * sqrt(CVa^2 + CVi^2)      # Z = 1.96 for 95% two-sided

The practical consequence is that an identical percentage change means very different things depending on the analyte. Within-subject variation is under 1% for serum sodium, so a 3% shift there is real. It is roughly 20% for TSH and similar for triglycerides, which means a 25% move between two draws may be nothing at all. Published CVi estimates belong in your analyte dictionary right next to the canonical unit, and the EFLM Biological Variation Database is the standard source. Your plots should shade the RCV band around the previous value rather than showing the population range alone.

The second rule is not to fit a trend to three points. With irregular sampling and heteroscedastic noise, a Theil-Sen estimator over five or more draws is more defensible than ordinary least squares, and a rolling median is better than either for display purposes. Annotate every point with the lab that produced it, because method changes produce step artifacts that look convincingly like biology. Ferritin, vitamin D, and testosterone immunoassays differ materially across platforms. A “trend” that begins exactly when you switched labs should therefore be treated as a calibration difference until proven otherwise.

The third rule is to record the pre-analytical state, since the conditions around a draw move real analytes. Fasting duration and time of day matter, and so do hydration and recent exercise. Eccentric exercise alone shifts plasma protein oxidation markers over a multi-day window after a single bout 1. The same logic applies to creatine kinase, ALT, and creatinine after hard training. A tracker that cannot answer “what had I done in the 48 hours before this draw?” will generate spurious findings.

Where continuous data and panels meet

Blood panels are sparse snapshots, while continuous glucose monitoring and wearable data arrive densely and continuously. Both belong in the same store, but the continuous streams should be resampled to daily features rather than merged at raw resolution. One useful cross-check is the glucose management indicator, GMI = 3.31 + 0.02392 × mean sensor glucose in mg/dL, compared against your measured HbA1c. Persistent disagreement between the two is informative about red cell turnover or assay interference, and it is worth raising with a clinician rather than resolving on your own.

The broader direction of travel is toward more frequent, lower-friction measurement, including wearable affinity biosensors that target proteins and hormones rather than only electrolytes and metabolites 2. Sweat-based multiplexed platforms already demonstrate battery-free wireless readout of several analytes at once 3, and reviews of the field are candid that calibration against venous blood remains the limiting problem 4. Longitudinal cohorts on digital health platforms do show measurable improvement in blood and fitness-tracker biomarkers over time, which is the practical case for tracking at all 5.

What tracking cannot do for you

It is worth being clear about the boundary of the exercise. A tracker tells you that a number moved and by how much. It cannot tell you why, and some modern blood assays carry diagnostic weight that no app should be interpreting. Plasma p-tau217 now performs comparably to cerebrospinal fluid testing for Alzheimer’s pathology 6, which is precisely the class of result that requires a clinician and a clinical context rather than a chart with a colored band. The same caution applies to anything hematologic, to any positive autoimmune serology, and to any result flagged as critical. The correct output of your pipeline is a clean, well-annotated history you can hand to a physician. It should not be issuing verdicts.

Build the boring parts well: identity mapping, unit canonicalization, censored-value handling, lab provenance, and RCV bands. Everything else is plotting.

Oak builds longitudinal molecular profiles of individuals: whole-genome sequencing, RNA sequencing, proteomics, blood biomarkers, and continuous glucose data, integrated into one model of you. Build your profile.

Footnotes

  1. Christopher James, Cory W. Dugan, Corrin Boyd, et al. Temporal tracking of cysteine 34 oxidation of plasma albumin as a biomarker of muscle damage following a bout of eccentric exercise. European Journal of Applied Physiology, 2024. https://doi.org/10.1007/s00421-024-05488-1 ↩

  2. Jiaobing Tu, Rebeca M. Torrente‐Rodríguez, Minqiang Wang, et al. The Era of Digital Health: A Review of Portable and Wearable Affinity Biosensors. Advanced Functional Materials, 2019. https://doi.org/10.1002/adfm.201906713 ↩

  3. Yong Yang, Xiaoling Lei, Dongliang Zhang, et al. A miniaturized battery-free wireless epidermal platform for multiplexed sweat biomarker monitoring. Chemical Engineering Journal, 2025. https://doi.org/10.1016/j.cej.2025.167787 ↩

  4. Dang-Khoa Vo, Kieu The Loan Trinh. Advances in Wearable Biosensors for Healthcare: Current Trends, Applications, and Future Perspectives. Biosensors, 2024. https://doi.org/10.3390/bios14110560 ↩

  5. Nimisha Schneider, Paul Fabian, Michelle Cawley, et al. Improvements in blood and fitness tracker biomarkers in a longitudinal real-world cohort of digital health platform users. PLOS Digital Health, 2026. https://doi.org/10.1371/journal.pdig.0001271 ↩

  6. Nicolas R. Barthélemy, Gemma Salvadó, Suzanne E. Schindler, et al. Highly accurate blood test for Alzheimer’s disease is similar or superior to clinical cerebrospinal fluid tests. Nature Medicine, 2024. https://doi.org/10.1038/s41591-024-02869-z ↩