Skip to content

FHIR Conventions & Known Issues

Bare FHIR code types

GitHub issue: croeder-fhir-to-omop/fhir-omop-ig#14

Background

Some FHIR R4 elements use the primitive code type rather than Coding or CodeableConcept. Examples:

Element FHIR path Implicit system
Patient gender Patient.gender http://hl7.org/fhir/administrative-gender
Allergy category AllergyIntolerance.category http://hl7.org/fhir/allergy-intolerance-category
Allergy type AllergyIntolerance.type http://hl7.org/fhir/allergy-intolerance-type

The system URI is implicit — it comes from the element's ValueSet binding in the specification, not from the resource data. In FHIR Mapping Language (FML), when you bind such an element to a source variable, the variable holds only the raw code string with no system attached.

The problem

The FML translate() function forwards the source value to the terminology server. When the source is a bare code with no system, the $translate request sent to enchilada contains code=male but no system parameter. enchilada cannot determine which vocabulary to search.

Attempts to construct a Coding object inline in FML using create('Coding') fail at runtime because the FML engine's variable scoping rules do not allow a variable created on the target side of a rule to be used as a source in a nested rule.

The convention

Use the IG ConceptMap canonical URL as the translate() mapUri:

src.gender as gender -> tgt.gender_concept_id =
    translate(gender, 'http://hl7.org/fhir/uv/omop/ConceptMap/GenderClass', 'code') "GenderConcept";

matchbox looks up the ConceptMap by canonical URL in the loaded IG packages. When found, it translates locally and never calls the terminology server. This works because bare code types have a fixed, finite set of values with stable OMOP mappings — they don't need runtime vocabulary lookup.

Why not pass the system URI as mapUri? matchbox uses the FML mapUri only to look up a ConceptMap locally. If no ConceptMap has that URL, matchbox calls the tx server with only the bare code value — the mapUri is not forwarded. So translate(gender, 'http://hl7.org/fhir/administrative-gender', 'code') sends enchilada {"code": "male"} with no system, which enchilada cannot resolve.

Allergy vs. intolerance category

AllergyIntolerance.category shares the same FHIR system URI for both allergy and intolerance records, but they map to different OMOP concept IDs. The IG provides two separate ConceptMaps:

AllergyIntolerance.type ConceptMap URL
allergy http://hl7.org/fhir/uv/omop/ConceptMap/AllergyType
intolerance http://hl7.org/fhir/uv/omop/ConceptMap/IntoleranceType

FML routes allergy and intolerance records to the correct ConceptMap based on .type.


EncounterVisit R4 Coding navigation

GitHub issue: croeder-fhir-to-omop/fhir-omop-ig#15

In FHIR R4, Encounter.class is a Coding (not a CodeableConcept). FML navigation must reference the Coding element directly:

// Correct (R4):
src.class as sc -> tgt.visit_concept_id = translate(sc, '', 'code') "VisitConcept";

// Wrong (R5-style):
src.class as cl -> cl.coding as sc -> tgt.visit_concept_id = translate(sc, '', 'code');

OMOP rules for supplemental concepts

Local (non-Athena) concepts added to enchilada must follow OMOP rules:

  1. concept_id > 2,000,000,000 — Athena assigns IDs ≤ 2B; local IDs must be > 2B.
  2. Maps to relationship — every local source concept must have a CONCEPT_RELATIONSHIP row mapping it to a standard (Athena) target concept.
  3. VOCABULARY table entry — each locally-defined vocabulary must appear in the VOCABULARY table for documentation.

Supplemental files (concept_extra.tsv, concept_relationship_extra.tsv, vocabulary_extra.tsv) are generated by mine_concept_maps.py in matchbox_scripts and satisfy all three rules automatically. Local concept IDs are assigned deterministically via SHA-256 hash of (vocabulary_id, concept_code), mapped into the range [2,000,000,000, 3,000,000,000).

On enchilada startup, all rows with concept_id > 2B are deleted and reloaded from the supplemental files. Athena rows (concept_id ≤ 2B) are loaded once and cached.


FHIR R4 only

enchilada targets FHIR R4. R5 resource patterns (e.g. MedicationStatement with medication.concept) are not supported and will fail at the matchbox transform step before ever reaching enchilada.


Terminology server strategy

There are two translation paths, used for different kinds of FHIR codes:

Code type FML pattern Runtime path
Coding / CodeableConcept (has system URI in data) translate(sc, '', 'code') matchbox → enchilada
Bare code (system is implicit, not in data) translate(code, 'IG-ConceptMap-URL', 'code') matchbox local ConceptMap lookup

enchilada handles clinical terminologies (SNOMED, LOINC, RxNorm, ICD-10-CM, CVX). The IG ConceptMaps handle bare FHIR administrative codes (gender, allergy category, encounter class, etc.) via local matchbox lookup — the terminology server is never called for those.