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
codevalue — the mapUri is not forwarded. Sotranslate(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:
- concept_id > 2,000,000,000 — Athena assigns IDs ≤ 2B; local IDs must be > 2B.
Maps torelationship — every local source concept must have aCONCEPT_RELATIONSHIProw mapping it to a standard (Athena) target concept.- VOCABULARY table entry — each locally-defined vocabulary must appear in the
VOCABULARYtable 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.