
Health data looks like any other data until real reports start arriving. Here is what tends to surprise engineering teams after launch, and what to check before it surprises yours.
If you have built integrations for payments, logistics or e-commerce, you already know how to move data reliably between systems. Health data asks for all of that and then adds a layer that is easy to underestimate: the meaning of a value depends on context that is often missing, inconsistent or buried in a document. A number can be perfectly transmitted and still be wrong for the person reading it.
None of the lessons below are exotic. They are the issues we see most often when teams move from a clean demo file to the reports their users actually send. Each section describes the problem, shows a short example, and ends with a check you can run against your own design.
Laboratories name tests for their own convenience, and those names change across labs, countries, languages and even analyser models within the same lab. A fasting glucose result can arrive under half a dozen labels, and a string match on "Glucose" will quietly merge it with random glucose, urine glucose and glucose tolerance results, which mean very different things clinically.
Six labels, one test. Your system needs to know they are the same thing, and that "Glucose, random" is not.
The practical fix is to identify results by what was measured, in what specimen and under what conditions, and to map each lab's local names to medical coding standards (plus any local code set your organisation maintains) rather than trusting the display name.
Check: if two labs send the same test under different names, will your system recognise them as one? If one lab sends two different tests under similar names, will it keep them apart?
Glucose of 5.4 is normal in millimoles per litre (mmol/L), the unit used in much of Asia, Europe and Australia, while 5.4 in milligrams per decilitre (mg/dL), common in the United States, would be dangerously low. Creatinine is reported in micromoles per litre in some countries and milligrams per decilitre in others, with a conversion factor of 88.4 between them. Glycated haemoglobin (HbA1c) is reported as a percentage in some labs and in mmol/mol in others, so 6.5% and 48 mmol/mol describe the same result1.
What often arrives
{ "test": "Glucose", "value": 5.4 }
What your system needs to keep
{
"test": "Glucose, fasting",
"value": 5.4,
"unit": "mmol/L",
"reference_range": { "low": 3.9, "high": 5.5, "unit": "mmol/L" }
}
If your product converts units for display or for trend charts, store the original value and unit alongside the converted one, so you can always trace back what the lab actually reported.
Check: can a value ever reach your database, a chart or an AI prompt without its unit attached?
It is tempting to hard-code "normal" ranges per test. In practice, laboratories are expected to establish or verify reference intervals for their own methods and populations2, and ranges often differ by age, sex and pregnancy status. Two labs can report the same value with different flags, and both can be right.
The safer pattern is to store the reference range the lab printed with each result, together with the lab's own high or low flag, and only fall back to a generic range when none was provided, clearly marked as such.
Check: if a user uploads reports from two labs, will your app flag the same value consistently with what each lab actually said?
Real reports contain values such as <5, >1000, Trace, Positive, Not detected and See comment. A column typed as a decimal will either reject these or, worse, silently turn <5 into 5. Fast Healthcare Interoperability Resources (FHIR), the most widely adopted modern standard for exchanging health data, handles this with a comparator on quantities and separate fields for text or coded answers3.
Keeping "less than 5" meaningful
"valueQuantity": { "comparator": "<", "value": 5, "unit": "mIU/L" }
A qualitative result
"valueString": "Trace"
Check: what does your system store when a lab sends "<5", and what does your chart draw for it?
A single lab report can include when the specimen was collected, when the lab received it, when the result was reported and when it was amended. For trends and clinical decisions, the collection date is usually the one that matters, yet it is often the least prominent date on the page. Date formats add another trap: 03/04/2026 is 3 April in Singapore and 4 March in the United States.
Four dates, four meanings. Picking the wrong one shifts every point on a trend chart.
Check: does your data model keep each date separately, and do you know which date format each source uses?
Labs correct results after release, for example when a sample is found to be haemolysed or a transcription error is caught. Both FHIR and HL7 Version 2 (the older messaging standard still used by most laboratory systems) carry a status for each result, such as preliminary, final, amended or corrected4, 5. If your integration inserts every message as a new row, a corrected result sits next to the wrong one and both appear in the patient's history.
Same result, second version
{
"resourceType": "Observation",
"identifier": [{ "value": "LAB-88213-GLU" }],
"status": "corrected",
...
}
Use the lab's own identifier to update the existing result, keep the earlier version for audit, and decide how you will tell users (and clinicians) that something they saw has changed.
Check: what happens in your system when the same result arrives twice with different values?
There is rarely a single identifier that every system shares. Naming conventions vary widely: many people across Asia write the family name first, some people have a single name, and names are transliterated differently between scripts. Dates of birth are sometimes estimated or recorded in a different calendar. A report can easily be attached to the wrong person, or a person can end up split across two records.
Design for this early. Decide which identifiers you require, what counts as a confident match, and what happens to a report when the match is uncertain. A queue for human review is far cheaper than an incorrect result in someone's record.
Check: where does a report go when your system is not sure who it belongs to?
Even where lab systems exchange structured messages internally, a large share of results reach digital health products as PDFs, scanned letters, emailed attachments and phone photos of paper reports. These arrive rotated, multi-page, in several languages, with tables that break across pages and handwritten notes in the margin.
Large language models make a first pass at extraction look easy on a clean sample. The difficulty shows up at volume: a value attached to the wrong test, a unit read from the adjacent column, a row skipped on page two. Whatever approach you use, you need a way to measure accuracy at field level and a route for uncertain extractions to be reviewed by someone clinically trained.
Check: do you know your field-level accuracy on real-world documents, and who reviews the results your system is unsure about?
Receiving data "in FHIR" or "in HL7" is a good start, but it does not mean two sources look the same. FHIR is designed to be adapted through profiles and extensions, so implementations differ in which fields they fill and how6. HL7 Version 2 feeds vary by site, often include custom segments, and put the same information in different fields. Many institutions also maintain their own code sets alongside national or international ones.
Treat each new source as its own integration. Ask for sample messages early, document how that source uses each field, and expect to map its local codes and conventions before the data is consistent with the rest of your system.
Check: if you added a second lab tomorrow, how much of your parsing and mapping would you need to redo?
Teams usually protect the production database well. Health data tends to leak through the side doors: request bodies written to application logs, error-tracking tools capturing payloads, real reports copied into test fixtures, screenshots in support tickets, and prompts sent to third-party AI services. Many jurisdictions also have rules about where health data may be processed, which affects which cloud regions and vendors you can use.
Pseudonymise identifiers before data reaches logs, prompts and test environments, use synthetic reports for development, and know the processing location of every service in your pipeline. We will go deeper on these patterns in a later guide in this series.
Check: search your logs and error tracker for a real patient name or date of birth. What do you find?
None of these lessons require exotic technology. They require treating context as part of the data: the unit, the range, the date, the status, the source and the person the result belongs to. Teams that plan for this before launch spend their time building features. Teams that meet it after launch spend months cleaning up records their users already trusted.
Code examples are illustrative and simplified. Reference ranges shown are examples only and are not clinical guidance.