Written and maintained by CASRAI Editorial Board
Last updated
A biobank shipment data model is the set of records and fields that lets a receiving site prove which specimens left, who held them at each hand-off, and what arrived. This guide is the field-level view: the entities to model, the fields each one needs, and how those fields line up with the HL7 FHIR resources Specimen, Task and Provenance. For the process side (who signs, when, and why), see chain of custody for research lab samples.
What a biobank shipment data model has to record
A shipment data model answers four questions for every specimen at every moment: what is it, where is it, who is responsible for it, and what happened to it last. Chain-of-custody tracking is the append-only answer to the last two. The model therefore needs two kinds of record: the current state (the shipment is in transit, the specimen is received) and an immutable history of events that explains how the state came to be.
Two design rules follow. First, keep state and history in separate structures so that a correction adds a new event instead of overwriting an old one. Second, give every specimen, container and shipment a stable identifier that is never reused, because every manifest line and custody event points back to one of them.
Core entities: shipment, package, specimen and custody event
A typical design uses four entities. This decomposition is a design pattern, not something a standard prescribes.
- Shipment: the transfer as a whole, with an origin, a destination, a sender, an intended recipient and a workflow status.
- Package (container): a box, tray or rack that travels as a unit and holds specimens or tubes.
- Specimen: the biological sample, with its own identifiers, type, status and links to the specimen it was derived from.
- Custody event: one hand-off or handling step, recording who, when, where and what activity.
Specimen identity and lineage belong in the specimen record, which is why a biospecimen keeps its own identifiers even when it moves between biorepositories. Software that implements these entities is covered in biobank software, and labelling is covered in barcode and RFID sample tracking.
Chain-of-custody event fields
The HL7 FHIR Provenance resource is a convenient vocabulary for a custody event, because it records who did what, to which resource, where and when. The table lists its elements as published in FHIR R5 (checked 2026-10-29) and what each means for a hand-off.
| Provenance element | Cardinality | Use in a custody event |
|---|---|---|
| target | 1..* | The specimen, container or shipment record the event refers to |
| occurred[x] | 0..1 | When the hand-off physically happened (a dateTime or a Period) |
| recorded | 0..1 | The instant the event was written into the system |
| location | 0..1 | Where the event took place |
| activity | 0..1 | What was done, for example dispatch or receipt |
| agent.who | 1..1 per agent (at least one agent required) | The person or organisation responsible for the event |
| agent.onBehalfOf | 0..1 | The organisation the agent was acting for, such as a courier’s employer |
The distinction between occurred and recorded matters: a receipt that happened at 09:10 but was keyed in at 14:30 has two different timestamps, and an audit needs both. A correction should be a new event that points at the earlier one, not an edit.
Mapping to HL7 FHIR Specimen, Task and Provenance
The FHIR Specimen resource (R5, Maturity Level 2, Trial Use) is described as a sample to be used for analysis. Its elements include identifier, accessionIdentifier, status, type, subject, receivedTime, parent, condition, a collection block, a processing block and a container block. It does not define dedicated transport or custody elements, and the specification states that it does not address the recursive nature of containers or the tracking of a container’s location within its parent container, such as a tube in a tray in a rack.
That gap is why a shipment model combines resources. One workable mapping, offered as a design pattern and not as something FHIR prescribes:
| Model concept | FHIR element | Notes from the specification |
|---|---|---|
| Specimen identity | Specimen.identifier (0..*), accessionIdentifier (0..1) | The accession identifier is the ID assigned by the laboratory at accessioning |
| Lineage (aliquot, derivative) | Specimen.parent (0..*) | Reference to the source specimen it was derived from or is a component of |
| Arrival at the testing site | Specimen.receivedTime (0..1) | When the testing laboratory received the specimen |
| Container position | Specimen.container.location (0..1) | The location of the container holding the specimen |
| Shipment workflow state | Task.status (1..1), businessStatus (0..1) | Task tracks the state of completion of an activity; its own example of businessStatus is “Specimen collected” |
| Who owns or requested the transfer | Task.owner, Task.requester (each 0..1) | Responsible party and requesting party |
| Transfer window | Task.executionPeriod (0..1) | Start and end of the activity |
| Each hand-off | Provenance (see table above) | One Provenance per event, targeting the Specimen or Task |
Fields such as courier name, tracking number and temperature at receipt are not dedicated elements of these resources; they are typical additions a site would carry as extensions or local fields. Choose them from your own transport procedures and the carrier’s documentation. The broader standard is explained in what is FHIR.
Worked example: manifest and receipt reconciliation
Reconciliation is the check that every manifest line resolves to a received specimen and vice versa. The numbers below are an illustrative calculation, not data from a real shipment.
A manifest lists 48 specimen IDs (M = 48). The receiving site scans 46 IDs (R = 46). Matching the two lists gives three sets:
- Matched (M ∩ R): 45
- Missing (M − R, on the manifest but not scanned): 48 − 45 = 3
- Unexpected (R − M, scanned but not on the manifest): 46 − 45 = 1
Check: matched + unexpected = 45 + 1 = 46 = R, and matched + missing = 45 + 3 = 48 = M. The match rate is 45 / 48 = 0.9375, or 93.75%.
Interpretation: reading the reconciliation result
| Result | What it tells you | Next record to write |
|---|---|---|
| Missing > 0 | Specimens left the sender’s custody without a confirmed arrival | A custody event for each missing ID, plus an open investigation on the Task |
| Unexpected > 0 | A specimen arrived that the sender’s manifest did not declare, or an ID was mis-scanned | A discrepancy event; do not create a new specimen until identity is resolved |
| Missing = 0 and Unexpected = 0 | The two lists agree on identity only | Receipt events for all lines; condition checks are a separate step |
A perfect match does not show that the specimens arrived in good condition; it shows that the identifiers agree.
Assumptions and common data-model mistakes
- Overwriting state: editing a status field without writing an event removes the history an audit needs.
- One timestamp for everything: collapsing occurred and recorded loses the ability to detect back-filled entries.
- Reusing identifiers: a reused ID makes old events point at the wrong specimen.
- Tracking only the box: if only the package is scanned, individual tubes can go missing unnoticed; reconcile at specimen level.
- Treating FHIR Specimen as a custody log: it has no dedicated custody elements, so custody needs Provenance or an equivalent event table.
- Assuming the identifier match is enough: reconciliation, as defined above, checks identity only.
For cross-border moves, transport classification and packaging sit outside this data model; see shipping biological samples internationally.
How to document the model in a protocol or data management plan
State the entities, the identifier scheme, the event fields with their required or optional status, and which fields are carried as local extensions. Name the standard and version you mapped to (for example, HL7 FHIR R5) and the date you checked it, and describe how corrections are recorded as new events.
How to calculate reconciliation in Excel, R or Python
Excel
Put manifest IDs in A2:A49 and scanned IDs in B2:B47. Then:
D1 Matched: =SUMPRODUCT(--(COUNTIF($B$2:$B$47,A2:A49)>0))
D2 Missing: =ROWS(A2:A49)-D1
D3 Unexpected: =SUMPRODUCT(--(COUNTIF($A$2:$A$49,B2:B47)=0))
R
manifest <- sprintf("S%03d", 1:48)
received <- c(setdiff(manifest, c("S007","S019","S033")), "S901")
matched <- intersect(manifest, received)
missing <- setdiff(manifest, received)
unexpected <- setdiff(received, manifest)
c(length(matched), length(missing), length(unexpected)) # 45 3 1
length(matched) / length(manifest) # 0.9375
Python
manifest = {f"S{i:03d}" for i in range(1, 49)}
received = (manifest - {"S007", "S019", "S033"}) | {"S901"}
matched = manifest & received
missing = manifest - received
unexpected = received - manifest
print(len(matched), len(missing), len(unexpected)) # 45 3 1
print(len(matched) / len(manifest)) # 0.9375
FAQ
What fields does a biobank shipment record need?
At minimum: a shipment identifier, origin and destination, sender and recipient, a workflow status, and one manifest line per specimen. Each custody event then needs a target, an occurred time, a recorded time, a location, an activity and a responsible agent, as in the Provenance table above.
Does FHIR Specimen track chain of custody?
No. In the FHIR R5 resource, the closest elements are container.location, receivedTime and collection.collector, and the resource has no dedicated transport or custody elements. Custody is usually modelled with Provenance and Task alongside it.
What is the difference between occurred and recorded?
Occurred is when the activity happened; recorded is the instant it was entered in the system. Keeping both makes late entries visible.
Sources
- HL7 International. FHIR R5, Specimen resource (v5.0.0, Maturity Level 2, Trial Use). hl7.org/fhir/specimen.html, checked 2026-10-29.
- HL7 International. FHIR R5, Provenance resource. hl7.org/fhir/provenance.html, checked 2026-10-29.
- HL7 International. FHIR R5, Task resource. hl7.org/fhir/task.html, checked 2026-10-29.








