FHIR to CDISC Joint Mapping Implementation Guide
0.1.0 - STU 1

This page is part of the CDISC Mapping FHIR IG (v0.1.0: STU 1 Ballot 1) based on FHIR R4. The current version which supercedes this version is 1.0.0. For a full list of available versions, see the Directory of published versions

Laboratory

This domain contains two mapping tables. The first is similar to the other domains and covers the SDTM and CDASH specification. The second covers the CDISC LAB specification. It is handled as a separate table because it has a significantly larger number of data elements than the other two specifications and the element names have less correlation. Readability of the mappings was enhanced by moving the content to a separate specification.

Lab data in FHIR is handled by two primary resources:

  • DiagnosticReport which covers the overall report of a collection of lab reports
  • Observation which handles the individual data measurements within an overall lab report

The CDISC specifications focus almost exclusively on the latter. As a result, mappings in both tables are expressed from the perspective of an Observation-rooted transformation. (I.e. All paths are rooted in Observation or are driven by a search based on Observation.) For studies interested in the retrieval of legacy lab information, it may in some cases be necessary to retrieve the DiagnosticReport and manually extract information from a PDF or other report representation. Obviously no standardized mapping can be provided here to assist with that.

In FHIR, the Observation resource is used for a wide range of data collection purposes. In addition to lab data, it is also used to capture vital signs, patient symptoms, psychological assessments, device data, and others. Ideally, lab data can be distinguished from types of Observations using the Observation.category element which should, ideally, have a code of laboratory drawn from the http://terminology.hl7.org/CodeSystem/observation-category. However, the core FHIR specification does not mandate the use of this code or system. (The U.S. Realm implementation guide and several other national implementation guides do mandate the use of this category.)

The 'laboratory' category encompasses both simple chemical measurements as well as complex assessments including the description of genetic variants, microbiology tests, etc. This implementation guide focuses only on simple measurements and does not attempt to map more complex structures - which in some cases correspond to distinct CDISC domains. In part, this is because FHIR has not yet tried to enforce standardized representation of more complex areas, though initial work has been completed on the capture of genetic findings. Future versions of this implementation guide will likely tackle more complex lab structures.

LB Mappings

Guidance on interpreting the table can be found here.

CDISC FHIR map (or gap) Comment
Name CDASH SDTM Element FHIRPath
Study ID or Number STUDYID STUDYID ResearchStudy.identifier

ResearchSubject.where(subject=Observation.subject).study.resolve().partOf.resolve().identifier.value

Observation.extension(workflow-researchstudy).valueReference.resolve().partOf.resolve().identifier.value

Mapping is based on presumption that research subject will be tied to site-specific ResearchStudy, which will then be part of overall ResearchStudy.

The path using the extension will only exist if the system maintaining the Observation is aware of its relevance to the Study.
Site ID or Number SITEID SITEID ResearchStudy.identifier

ResearchSubject.where(subject=Observation.subject).study.resolve().identifier.value

Observation.extension(workflow-researchstudy).valueReference.resolve().identifier.value

Mapping is based on presumption that research subject will be tied to site-specific ResearchStudy, which will then be part of overall ResearchStudy.

The path using the extension will only exist if the system maintaining the Observation is aware of its relevance to the Study.
Investigator ID or Number INVID INVID Practitioner.identifier

ResearchSubject.where(subject=Observation.subject).study.resolve().principleInvestigator.resolve().identifier.value

Observation.extension(workflow-researchstudy).valueReference.resolve().principleInvestigator.resolve().identifier.value

Will need to decide which id to expose. If you want the PI for the overall study rather than just for the site associated with the Observation, you'll need to traverse the partOf.resolve() from the study-specific ResearchStudy.

The path using the extension will only exist if the system maintaining the Observation is aware of its relevance to the Study.
Screen ID or Number (pre-randomization) SUBJID SUBJID ResearchSubject.identifier

ResearchSubject.where(individual=Observation.subject).identifier.value

Study subject is found by finding the StudySubject that corresponds to the same Patient and ResearchStudy as the Observation.

No standard way to decide which subject identifier to use
Visit ID or Number VISITNUM VISITNUM Encounter.identifier

Observation.encounter.resolve().identifier

Visit Name VISIT VISIT ActivityDefinition.name

Observation.encounter.resolve().extension(workflow-instantiates).valueCanonical.resolve().title

We could go through Encounter.basedOn-> ServiceRequest.instantiatesCanonical, you arrive at ActivityDefinition.

Encounter should have a standard instantiatesCanonical extension that would allow pointing to the ActivityDefinition. This would be the ActivityDefinition.name. Submit a change request to add the extension
Accession ID or Number LBREFID LBREFID Specimen.accessionIdentifier

Observation.specimen.resolve().accessionIdentifier.value

Specimen Actual Collection Start Date and Time LBDTC Specimen.collection.collectedPeriod

Observation.specimen.resolve().collection.collectedPeriod.start

Specimen Actual Collection End Date and Time LBENDTC Specimen.collection.collectedPeriod

Observation.specimen.resolve().collection.collectedPeriod.end

Specimen Collection Duration (for Clinical Research with extended time of collection) LBCDUR LBDUR Specimen.collection.collectedPeriod

Observation.specimen.resolve().collection.collectedPeriod

Specimen Planned Time Point Name (Planned Collection Time Point - time within Encounter) LBCTPT LBTPT Specimen.extension

Observation.specimen.resolve().extension(cqf-relativeDateTime).valueReference.reference

TBD - change being made to Specimen resource
Specimen Planned Elapsed Time from Time Point Reference LBCELTM LBELTM Specimen.extension

Observation.specimen.resolve().extension(cqf-relativeDateTime).valueReference.reference

TBD - change being made to Specimen resource
Specimen Condition LBCSPCCN LBSPCCND Specimen.condition

Observation.specimen.resolve().condition.coding.code

TBD - change being made to Specimen resource

Extend the FHIR vocabulary to include CDISC values. NOTE: requires an extension to the FHIR vocabulary to include CDISC values (e.g., Refrigerated)
Specimen Type LBCSPEC LBSPEC Specimen.type

Observation.specimen.resolve().type.coding.display

NOTE: In some cases, this may be inferred from the LOINC.
Fasting Status LBCFAST LBFAST Specimen.collection.fastingStatus

Observation.specimen.resolve().collection.fastingStatus.coding.code

Condition Met (Y/N) LBCOND This is an 'ask on entry' question. May be able to determine some conditions, such as fasting, by using other specimen.collection attributes (e.g., fasting status). Others might be captured as components or extensions. Alternatively, obtain this information from the case report form entries. There are explicit elements for Specimen.collection.fastingStatus and collection.fastingDuration. May need extensions for other pre-conditions
Specimen Collection Position LBCPOS LBPOS Specimen.colelction.extension

Observation.specimen.resolve().collection.extension(observation-bodyPosition).valueCodeableConcept

Specimen Usability for the Test LBCSPCUF LBSPCUFL Specimen.status

Observation.specimen.resolve()status.where($this='Unsatisfactory')

Category LBCCAT LBCAT DiagnosticReport.category.code

DiagnosticReport.category.code.where(system='LBSCAT')

NOTE: In some cases, this may be inferred from the LOINC.
DiagnosticReport.code

DiagnosticReport.code.text

Test Status LBCSTAT LBSTAT ServiceRequest.doNotPerform

Observation.basedOn.resolve().doNotPerform

Sponsor may choose to map a situation in which the entire panel is cancelled as one summary record compliant with SDTM IG examples. Some sponsor may decline to accept cancelled records under specific circumstances (e.g., unscheduled) Sponsor may also choose to populate LBREASND as CANCELLED
ServiceRequest.status

Observation.basedOn.resolve().status

Reason Test Not Done LBREASND Observation.dataAbsentReason

Observation.dataAbsentReason.text

see notes on LBSTAT
Test Code LBTESTCD Observation.code

Observation.code.coding.where(selected=true).code

Test Name LBTEST Observation.code

Observation.code.coding.where(selected=true).display

Test LOINC Code LBLOINC LBLOINC Observation.code

Observation.code.coding.where(system='LOINC').code

Performing Laboratory Name LBNAM LBNAM Organization.name

Observation.performer.where($this is Organization).resolve().name

Reference Range Indicator LBCNRIND LBNRIND Observation.interpretation

Observation.interpretation.where(system ='NRIND')

Toxicity Grade LBTOXGR Observation.interpretation

Observation.interpretation.where(system=[Toxicity Grade]).coding.code

Toxicity Grade Code List Version LBCTOXV LBTOX - 2 Observation.interpretation

Observation.interpretation.where(system=[Toxicity Grade]).coding.version

Toxicity Grade Code List LBCTOX LBTOX - 1 Observation.interpretation

Observation.interpretation.where(system=[Toxicity Grade]).system

Reported Reference Range Low LBORNRLO LBORNRLO Observation.referenceRange

Observation.referenceRange.low

Reported Reference Range High LBORNRHI LBORNRHI Observation.referenceRange

Observation.referenceRange.high

Reference Range (for string value) LBCSTNRC LBSTNRC Observation.referenceRange

Observation.referenceRange.text

Result Value (numeric) LBORRES LBORRES Observation.valueQuantity

Observation.valueQuantity.value

Might also include valueQuantity.comparator
Result Value (string) LBORRES LBORRES Observation.valueCodeableConcept

Observation.valueCodeableConcept.text

Reported Units LBRESU LBORRESU Observation.valueQuantity

Observation.valueQuantity.unit

Result Clinically Significant (Y/N) LBCLSIG SUPPLB.QVAL Observation.interpretation

Observation.interpretation

Method LBCMETH LBMETHOD Observation.method

Observation.method

Anatomical Region LBLOC Specimen.bodySite

Observation.specimen.resolve().collection.bodySite

Run ID (ties data together across subjects) LBRUNID LBRUNID Would need to introduce an extension
Unique Subject ID for Sponsor USUBJID ResearchSubject.identifier

ResearchSubject.where(individual=Observation.subject).identifier.value

To distinguish SUBJID from USUBJID, would need to look at identifier.system
Result Categorization LBRESCAT LBRESCAT This wouldn't come from the lab.
Anatomical Region LBANTREG LBANTREG Specimen.collection.bodySite

Observation.specimen.resolve().collection.bodySite

Unscheduled Flag LBUSCHFL LBUSCHFL This can sort of be inferred from the absence of a link to a PlanDefinition or ActivityDefinition, however there are lots of other reasons these links could be missing, so that's not totally reliable. Could look at using an extension or a tag
Planned Time Point Number (sequence to order the relative timepoints) LBTPTNUM Pull this information from the Clinical Plan (PlanDefinition) Link to the clinical plan would be through the Observation.basedOn link to CarePlan or the instantiatesCanonical link to PlanDefinition or ActivityDefinition
Analytical Method LBANMETH This does not apply to safety labs. The corresponding values need to be evaluated and aligned accordingly to the CDISC context of --METHOD. Potentially could appear in Observation.method. An extension is required to distinguish.
Baseline Flag LBBLFL Variable may be deprecated. This information would come from the Sponsor. Could appear in Observation.reasonCode. In practice the notion of 'baseline' is more of a relationship. Any arbitrary Observation might be selected as the 'baseline' for a particular step in the study. As such, the real linkage is the tie to the CarePlan activity that's tied to the ActivityDefinition with a reason of 'baseline'.
Derived Flag LBDRVFL This information would come from the Sponsor. FHIR doesn't use a flag. If an Observation is derived, it should point to the Observations it's derived from with the derivedFrom association. (If that relationship is present, then the Derived Flag is true)

LAB Mappings

Guidance on interpreting the table can be found here.

CDISC FHIR map (or gap) Comment
Name LAB Element FHIRPath
Study ID or Number /GTP/Study/@ID ResearchStudy.identifier

ResearchSubject.where(subject=Observation.subject).study.resolve().partOf.resolve().identifier.value

Observation.extension(workflow-researchstudy).valueReference.resolve().partOf.resolve().identifier.value

Mapping is based on presumption that research subject will be tied to site-specific ResearchStudy, which will then be part of overall ResearchStudy.

The path using the extension will only exist if the system maintaining the Observation is aware of its relevance to the Study.
Study Name /GTP/Study/@Name ResearchStudy.title

ResearchSubject.where(subject=Observation.subject).study.resolve()partOf.resolve().title

Observation.extension(workflow-researchstudy).valueReference.resolve().partOf.resolve().title

Mapping is based on presumption that research subject will be tied to site-specific ResearchStudy, which will then be part of overall ResearchStudy.

The path using the extension will only exist if the system maintaining the Observation is aware of its relevance to the Study.
Transmission Type /GTP/Study/@TransmissionType Determine from type of call to service (requesting all data vs incremental data). NOTE: If data are not persisted between FHIR and LAB, cannot do incremental data processing (due to lack of delta detection). If the data is being 'pushed' in a message, this could also be conveyed in the MessageHeader.event
Study Transaction Type /GTP/Study/TransactionType Determining whether transactions are insert or update is a task for the data consumer. (NOTE: This requires persistent storage to compare current data with data in the incoming file) This would be conveyed using a transaction Bundle - specifically the Bundle.entry.request.method
Site ID or Number /GTP/Study/Site/@ID ResearchStudy.site

ResearchSubject.where(subject=Observation.subject).study.resolve().identifier.value

Observation.extension(workflow-researchstudy).valueReference.resolve().identifier.value

Mapping is based on presumption that research subject will be tied to site-specific ResearchStudy, which will then be part of overall ResearchStudy.

The path using the extension will only exist if the system maintaining the Observation is aware of its relevance to the Study.
Site Transaction Type /GTP/Study/Site/TransactionType Determining whether transactions are insert or update is a task for the data consumer. (NOTE: This requires persistent storage to compare current data with data in the incoming file) As per study transaction type (not clear why it happens at both levels - what would it mean to 'delete' at the study level and 'update at the site level?)
Investigator ID or Number /GTP/Study/Site/Investigator/@ID Practitioner.identifier

ResearchSubject.where(subject=Observation.subject).study.resolve().principleInvestigator.resolve().identifier.value

Observation.extension(workflow-researchstudy).valueReference.resolve().principleInvestigator.resolve().identifier.value

Will need to decide which id to expose. If you want the PI for the overall study rather than just for the site associated with the Observation, you'll need to traverse the partOf.resolve() from the study-specific ResearchStudy.

The path using the extension will only exist if the system maintaining the Observation is aware of its relevance to the Study.
Investigator Name /GTP/Study/Site/Investigator/@Name Practitioner.name.text

ResearchSubject.where(subject=Observation.subject).study.resolve().principleInvestigator.resolve().name.text

Observation.extension(workflow-researchstudy).valueReference.resolve().principleInvestigator.resolve().name.text

Will need to decide which name to expose. Can also extract the prefix, given and family names if text isn't specified.

The path using the extension will only exist if the system maintaining the Observation is aware of its relevance to the Study.
Investigator Transaction Type /GTP/Study/Site/Investigator/TransactionType Determining whether transactions are insert or update is a task for the data consumer. (NOTE: This requires persistent storage to compare current data with data in the incoming file) As per study transaction type (not clear why it happens at both levels - what would it mean to 'delete' at the study level and 'update at the site level?)
Screen ID or Number /GTP/Study/Site/Investigator/Subject/ScreenID ResearchSubject.identifier

ResearchSubject.where(subject=Observation.subject).identifier.value

Study subject is found by finding the StudySubject that corresponds to the same Patient and ResearchStudy as the Observation.

No standard way to decide which subject identifier to use. Also no standard way to differentiate subject id from screening id from spare subject id.
Subject ID or Number /GTP/Study/Site/Investigator/Subject/SubjectID ResearchSubject.identifier

ResearchSubject.where(subject=Observation.subject).identifier.value

Study subject is found by finding the StudySubject that corresponds to the same Patient and ResearchStudy as the Observation.

No standard way to decide which subject identifier to use. Also no standard way to differentiate subject id from screening id from spare subject id.
Spare subject level ID or Number /GTP/Study/Site/Investigator/Subject/SpareSubjectID ResearchSubject.identifier

ResearchSubject.where(subject=Observation.subject).identifier.value

Study subject is found by finding the StudySubject that corresponds to the same Patient and ResearchStudy as the Observation.

No standard way to decide which subject identifier to use. Also no standard way to differentiate subject id from screening id from spare subject id.
Subject Sex /GTP/Study/Site/Investigator/Subject/Sex/@Value Patient.gender

ResearchSubject.where(subject=Observation.subject).individual.resolve().gender

Subject Sex Code List ID /GTP/Study/Site/Investigator/Subject/Sex/@CodeListID For FHIR, the administrative gender code list is fixed, so no need to send it in the instance. If there's a need to convey alternate gender codes, then those would appear either as Observation values or as extension values on Patient (the former for clinical information, the latter for administrative purposes). In either event, the code would be in CodeableConcept.coding which allows identifying both the code system and (if relevant), the version
Subject Race /GTP/Study/Site/Investigator/Subject/Race/@Value There's an US-core extension (us-core-race) for capturing this in the U.S. and the possibility that other countries will define their own extensions. Note that this is for administrative, not clinical purposes. Genetic heritage would typically be captured as an Observation
Subject Race Code List ID /GTP/Study/Site/Investigator/Subject/Race/@CodeListID Race, whether captured as an extension or observation value is generally a CodeableConcept allowing capturing both code system and (if relevant) version. Race is highly variable from country to country (both whether it's allowed and how it's coded), so there's no standard element for this in the core spec.
Subject Initials /GTP/Study/Site/Investigator/Subject/Confidential/@Initials Patient.name.text

ResearchSubject.where(subject=Observation.subject).individual.resolve().name.text

Note that name.text will typically contain the full name, but *can* contain initials only. If the full name is present, initials can be extracted by looking at the given and family name components and converting to initials
Subject Date Of Birth /GTP/Study/Site/Investigator/Subject/Confidential/@Birthdate Patient.birthDate

ResearchSubject.where(subject=Observation.subject).individual.resolve().birthDate

Note that precision can vary (YYYY, YYYY-MM or YYYY-MM-DD)
Visit ID or Number /GTP/Study/Site/Investigator/Subject/Visit/@ID Encounter.identifier

Observation.encounter.resolve().identifier.value

No standard way to decide which identifier to use if multiples are present
Visit Name /GTP/Study/Site/Investigator/Subject/Visit/@Name In practice, visit name would be the ActivityDefinition.title for the activity in the protocol associated with the encounter. There is no standard extension for this link (though one will be defined). Most clinical systems won't actually capture this, so the determination will need to be made at time of data extraction based on the protocol See LB mapping comment
Visit Type /GTP/Study/Site/Investigator/Subject/Visit/@Type This is essentially whether the visit is tied to a particular activity within the PlanDefinition (study protocol) or not. Given that in non-study-specific systems, there won't typically be a linkage even when the encounter *is* driven by the study, this will generally need to be populated algorithmically on extension This could theoretically be distinguished by whether there was a link to a CarePlan activity or ActivityDefinition. Alternatively, you could use an extension or tag. (What's 'planned' for one study might be unplanned for another)

This would be Encounter.reasonCode
Visit Type Modifier /GTP/Study/Site/Investigator/Subject/Visit/@TypeModifier Encounter.reasonCode

Observation.encounter.resolve().reasonCode

This would be Encounter.reasonCode
Subject Transaction Type /GTP/Study/Site/Investigator/Subject/TransactionType Determining whether transactions are insert or update is a task for the data consumer. (NOTE: This requires persistent storage to compare current data with data in the incoming file) As per study transaction type (not clear why it happens at both levels - what would it mean to 'delete' at the study level and 'update at the site level?)
Accession ID or Number /GTP/Study/Site/Investigator/Subject/Visit/Accession/@ID Specimen.accessionIdentifier

Observation.resolve().accessionIdentifier.value

Last Active Date and Time /GTP/Study/Site/Investigator/Subject/Visit/Accession/@LastActiveDateTime FHIR allows capture meta.lastUpdated which reflects when the data last changed on the server in question, but would need to look at Provenance to see when data last changed on a particular system. This typically won't be available.
Visit Transaction Type /GTP/Study/Site/Investigator/Subject/Visit/Accession/TransactionType Determining whether transactions are insert or update is a task for the data consumer. (NOTE: This requires persistent storage to compare current data with data in the incoming file) As per study transaction type (not clear why it happens at both levels - what would it mean to 'delete' at the study level and 'update at the site level?)
Central Laboratory ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/CentralLab/@ID Organization.identifier

Observation.performer.resolve().identifier.value

Will need to look at identifier.type or identifier.system to know which identifier to use. In some cases, performer might be PractitionerRole, in which case, will need to map through PractitionerRole.organization. If there are multiple performers that link to multiple organizations, converter will need to look at Organization.type or have other rules to decide amongst the candidates.
Central Laboratory Name /GTP/Study/Site/Investigator/Subject/Visit/Accession/CentralLab/@Name Organization.name

Observation.performer.resolve().name

If there are multiple performers that link to multiple organizations, converter will need to look at Organization.type or have other rules to decide amongst the candidates.
Specimen ID or Number /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/@ID Specimen.identifier

Observation.specimen.resolve().identifier.value

If there are multiple identifiers, need to look at identifier.system or identifier.type to determine
Actual Collection Date and Time /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenCollection/@ActualCollectionDateTime Specimen.collection.collectedPeriod

Observation.specimen.resolve().collection.collectedPeriod.start

Planned Collection Time Elapsed /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenCollection/@PlannedCollectionTimeElapsed Specimen.extension

Observation.specimen.resolve().extension(cqf-relativeDateTime).valueDuration.code

Specimen.extension.extension.url=offset, then Specimen.extension.extension.valueDuration.value + Specimen.extension.extension.valueDuration.code
Planned Collection Time Elapsed Description /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenCollection/@PlannedCollectionTimeElapsedDescription Specimen.extension

Observation.specimen.resolve().extension(cqf-relativeDateTime).valueReference.reference

Specimen.extension.extension.url=relationship, then Specimen.extension.extension.valueString + Specimen.extension.extension.url=target, then Specimen.extension.extension.valueReference.reference
Collection End Date and Time /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenCollection/@CollectionEndDateTime Specimen.collection.collectedPeriod

Observation.specimen.resolve().collection.collectedPeriod.end

Received Date and Time /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenTransport/@ReceivedDateTime Specimen.receivedTime

Observation.specimen.resolve().receivedTime

Specimen Condition /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenTransport/@SpecimenCondition Specimen.condition

Observation.specimen.resolve().condition.coding.code

Will need to choose which coding to extract the code for
Investigator - Specimen Comment Source /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenComment/@Source Practitioner.identifier

Observation.specimen.resolve().note.author.resolve().where($thisisPractitioner).identifier

Note that practitioners can have multiple identifiers - use type or system to decide which identifier to expose
Investigator - Specimen Comment Text /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenComment/@Text Specimen.note.text

Observation.specimen.resolve().where($author.resolve()isPractitioner).note.text

Lab - Specimen Comment Source /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenComment/@Source Observation.identifier

Observation.specimen.resolve().note.author.resolve().where($thisisOrganization).identifier

Note that organizations can have multiple identifiers - use type or system to decide which identifier to expose
Lab - Specimen Comment Text /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenComment/@Text Specimen.note

Observation.specimen.resolve().where($author.resolve() isOrganization).note.text

Specimen Material ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenMaterial/@ID Specimen.type

Observation.specimen.resolve().where($author.resolve() isOrganization).type.coding.code

If multiple codes are present, filter based on system
Specimen Material Name /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenMaterial/@Name Specimen.type

Observation.specimen.resolve().where($author.resolve() isOrganization).type.coding.display

If multiple codes are present, filter based on system.
Specimen Material Code List ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SpecimenMaterial/@CodeListID Specimen.type

Observation.specimen.resolve().where($author.resolve() isOrganization).type.coding.system

If multiple codings are present, will need to decide which to use
Subject Age at Collection /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SubjectAtCollection/@AgeAtCollection If captured as an Observation, this would be in the ValueQuantity.code derived from the patient.birthDate(Observation.subject (ref:Patient.birthDate).

If the full birthdate cannot be shared, due to country restrictions, use the birth year to derive age information. Due to lost accuracy in having only the year of birth, 'years' would be the default unit.
Subject Age Units /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SubjectAtCollection/@AgeUnits If captured as an Observation, this would be in the ValueQuantity.code derived from the patient.birthDate(Observation.subject (ref:Patient.birthDate).

If the full birthdate cannot be shared, due to country restrictions, use the birth year to derive age information. Due to lost accuracy in having only the year of birth, 'years' would be the default unit.
Fasting Status /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/SubjectAtCollection/@FastingStatus Observation.specimenRefSpecimen.collection.fastingStatus

Observation.specimenRefSpecimen.collection.fastingStatusCodeableConcept.code

Battery ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/@ID DiagnosticReport.identifier

DiagnosticReport.identifier.value

Battery Name /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/@Name DiagnosticReport.code

DiagnosticReport.code.text

Battery Transaction Type /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/TransactionType Determining whether transactions are insert or update is a task for the data consumer. (NOTE: This requires persistent storage to compare current data with data in the incoming file) As per study transaction type (not clear why it happens at both levels - what would it mean to 'delete' at the study level and 'update at the site level?)
Test Status /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/@Status ServiceRequest.doNotPerform

Observation.basedOn.resolve().doNotPerform

ServiceRequest.status

Observation.basedon.resolve().status

Testing Date and Time /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/@TestingDateTime Observation.effectiveDateTime

Observation.effectiveDateTime

Test Type /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/@TestType The normal transmission of data, via FHIR, is for study tests. If non-study test results are required (e.g., AdverseEvent follow-up), these would be obtained via a special request from the data provider. Alternatively, if the system requires this field to be populated, derive whether the test was for the study or not, use the ServiceRequest resource. Differentiation would be whether the Observation was basedOn a particular activity in the CarePlan (scheduled) or tied to a particular ActivityDefinition (Study test)
Performing Laboratory ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/PerformingLab/@ID Organization.identifier

Observation.performer.resolve().identifier

Use "Organization/type" to pick the type of Organization that represents "Performing Lab"
Performing Laboratory Name /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/PerformingLab/@Name Organization.name

Observation.performer.resolve().name

Lab Test ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/LabTest/@ID Observation.code

Observation.code.coding.where(system = 'Lab Test')

Lab Test Name /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/LabTest/@Name Observation.code

Observation.code.coding.display

Additional Test Description /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/LabTest/@AdditionalDescription Observation.comment

Observation.comment

Receiver Test ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/ReceiverTest/@ID Observation.code

Observation.code.coding.where (system = Recipient Test)

Observation/code/coding/system/@value ("system" element used to designate the type of code that is being represented (in this case the "ReceiverTest") by the "code" element)
Receiver Test Name /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/ReceiverTest/@Name Observation.code

Observation.code.coding.display where (coding system = 'Recipient Test')

LOINC Code /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/LOINCTestCode/@Value Observation.code

Observation.code.coding.where (coding system = 'LOINC')

LOINC Code List ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/LOINCTestCode/@CodeListID Observation.code

Observation.code.coding.where(system = 'http://loinc.org')

Test Level Comments /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/TestLevelComment Observation.extension

Observation.extension(event-note).valueAnnotation.text

New extension from Observation
Reported Result Status /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/@ReportedResultStatus Observation.status

Observation.status

Alert Flag /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/@AlertFlag Observation.interpretation

Observation.interpretation.coding.where(system='Alert Flag')

Delta Flag /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/@DeltaFlag Observation.interpretation

Observation.interpretation.coding.where (system = 'Delta Flag')

Exclusion Flag /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/@ExclusionFlag Observation.interpretation

Observation.interpretation.coding.where (system = 'Exclusion Flag')

Blinding Flag /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/@BlindingFlag Observation.meta.security

Observation.meta.security.code

Reported Date and Time /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/@ReportedDateTime Observation.issued

Observation.issued

Test Transaction Type /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/TransactionType Determining whether transactions are insert or update is a task for the data consumer. (NOTE: This requires persistent storage to compare current data with data in the incoming file) As per study transaction type (not clear why it happens at both levels - what would it mean to 'delete' at the study level and 'update at the site level?)
Toxicity Grade /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/ToxicityGrade/@Value Observation.interpretation

Observation.interpretation.coding.where (system = 'Toxicity Grade')

Toxicity Grade Code List ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/ToxicityGrade/@CodeListID Observation.interpretation

Observation.interpretation.coding.where (system = 'Toxicity Grade')

Reported Result Type (C=coded; N=numeric; T=text; R=range; G = GT; L = LT) /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/@ResultType Determine from the field in which the result resides. e.g., Observation/category/coding/code/@value For text and code results, which both use CodeableConcept, look at the field within CC to determine code vs. text.
Text Result /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/TextResult/@Value Observation.valueCodeableConcept

Observation.valueCodeableConcept.text

Text Result Code List ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/TextResult/@CodeListID Observation.valueCodeableConcept

Observation.valueCodeableConcept.coding.system

Conventional Numeric Result /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/NumericResult/@Value Observation.valueQuantity

Observation.valueQuantity.value

Reported Numeric Result /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/NumericResult/@Value Observation.valueQuantity

Observation.valueQuantity.value

SI Numeric Result /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/NumericResult/@Value Observation.valueQuantity

Observation.valueQuantity.value

Conventional Numeric Result Precision /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/NumericResult/@Precision Observation.valueQuantity

Observation.valueQuantity.value

Determine the precision from the value.

Precision is implicitly determined from the attribute Observation/valueQuantity/value/@value
Reported Numeric Result Precision /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/NumericResult/@Precision Observation.valueQuantity

Observation.valueQuantity.value

Determine the precision from the value.

Precision is implicitly determined from the attribute Observation/valueQuantity/value/@value
SI Numeric Result Precision /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/NumericResult/@Precision Observation.valueQuantity

Observation.valueQuantity.value

Determine the precision from the value.

Precision is implicitly determined from the attribute Observation/valueQuantity/value/@value
Conventional Reference Range Low /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultReferenceRange/@ReferenceRangeLow Observation.referenceRange

Observation.referenceRange.low.value

Observation.referenceRange.low.valueunit

Reported Reference Range Low /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultReferenceRange/@ReferenceRangeLow Observation.referenceRange

Observation.referenceRange.low.value

Observation.referenceRange.low.valueunit

SI Reference Range Low /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultReferenceRange/@ReferenceRangeLow Observation.referenceRange

Observation.referenceRange.low.value

Observation.referenceRange.low.valueunit

Conventional Reference Range High /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultReferenceRange/@ReferenceRangeHigh Observation.referenceRange

Observation.referenceRange.high.value

Observation.referenceRange.high.valueunit

Reported Reference Range High /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultReferenceRange/@ReferenceRangeHigh Observation.referenceRange

Observation.referenceRange.high.value

Observation.referenceRange.high.valueunit

SI Reference Range High /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultReferenceRange/@ReferenceRangeHigh Observation.referenceRange

Observation.referenceRange.high.value

Observation.referenceRange.high.valueunit

Conventional Units /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultUnits/@Value Observation.valueQuantity

Observation.valueQuantity.unit

Reported Units /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultUnits/@Value Observation.valueQuantity

Observation.valueQuantity.unit

SI Units /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultUnits/@Value Observation.valueQuantity

Observation.valueQuantity.unit

Conventional Units Code List ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultUnits/@CodeListID Observation.valueQuantity

Observation.valueQuantity.system

Reported Units Code List ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultUnits/@CodeListID Observation.valueQuantity

Observation.valueQuantity.system

SI Units Code List ID /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultUnits/@CodeListID Observation.valueQuantity

Observation.valueQuantity.system

Record Extension Type Populate with a default value, depending on the type of data being transmitted.

E.g., "BASE"
Result Class /GTP/Study/Site/Investigator/Subject/Visit/Accession/BaseSpecimen/BaseBattery/BaseTest/BaseResult/SingleResult/ResultClass/@Value Derive this from the unit of measure system (on the result value), provide only one type of result, or provide a way to differentiate which results are reported, conventional, or international units. In some cases, the result unit of measure may provide some indication of whether the results are in conventional or SI.
Model Version /GTP/@ModelVersion Populate with a default value. The version of FHIR in use is conveyed using Resource.meta.profile
File Creation Date and Time /GTP/@CreationDateTime Pull from the message wrapper. (file creation date) Bundle.timestamp
Transaction Type /GTP/TransactionType No mitigation. If required, fill with a default value. As per study.transaction
Transmission Source ID /GTP/TransmissionSource/@ID Pull from the message wrapper. MessageHeader.source.endpoint if using messaging, otherwise determined out of band based on sender authentication process
Transmission Source Name /GTP/TransmissionSource/@Name Pull from the message wrapper. MessageHeader.source.name if using messaging, otherwise determined out of band based on sender authentication process