FHIR Mapper
This guide is for developers who want to transform data between different structures using Fhircraft's FHIR Mapping Language implementation. You'll learn to convert legacy system data to FHIR resources, transform between FHIR profiles, and handle complex data transformations with automatic validation.
Prerequisites
Before working with the FHIR Mapper, you should understand:
- FHIR resource basics from FHIR Resources Overview
- Creating and validating resources from Resource Models
- Basic FHIR Mapping Language concepts - See the official specification
Overview
Fhircraft's FHIR Mapper implements the official FHIR Mapping Language, enabling you to write declarative transformation rules that reliably convert data while maintaining FHIR compliance and type safety.
All mapped data is automatically validated using the resource models you learned about in previous sections, ensuring output compliance with FHIR specifications.
Early FHIR Feature
The FHIR Mapping Language specification is specified as a Maturity Level 0 (Draft)
, meaning that both the language specification and its implementations are bound to change in the future.
Quick Start
Transform legacy patient data to a FHIR Patient resource:
from fhircraft.fhir.mapper import FHIRMapper
# Legacy system data
legacy_patient = {
"firstName": "Alice",
"lastName": "Johnson",
"dob": "1985-03-15",
"sex": "F"
}
# Mapping script
mapping_script = """
map 'http://example.org/legacy-to-fhir' = 'LegacyPatient'
group main(source legacy, target patient: Patient) {
legacy.firstName -> patient.name.given;
legacy.lastName -> patient.name.family;
legacy.dob -> patient.birthDate;
legacy.sex where("$this = 'F'") -> patient.gender = 'female';
legacy.sex where("$this = 'M'") -> patient.gender = 'male';
}
"""
# Execute transformation
mapper = FHIRMapper()
targets, metadata = mapper.execute_mapping(mapping_script, legacy_patient)
patient = targets[0]
print(f"Transformed: {patient.name[0].given[0]} {patient.name[0].family}")
Understanding FHIR Mapping
The FHIR Mapping Language provides a declarative way to transform data between different structures. Think of it as writing rules that describe how to copy, transform, and validate data from source formats into FHIR-compliant resources.
Key Benefits
Declarative Approach: Instead of writing imperative code with loops and conditions, you declare what the end result should look like. The mapper handles the execution details.
Automatic Validation: Every transformation automatically validates the output against FHIR constraints, catching errors before they reach your application.
Maintainable Rules: Mapping rules are easier to read, modify, and audit compared to custom transformation code.
Standards Compliance: Built on the official FHIR specification, ensuring compatibility with other FHIR systems. The maps are FHIR-compliant and can be used within other FHIR systems outside of Fhircraft.
When to Use FHIR Mapper
Use FHIR Mapper when:
- Converting legacy system data to FHIR resources
- Transforming between different FHIR profiles (e.g., US Core to International)
- Standardizing data from multiple sources with different schemas
- Need audit trails and transformation metadata
- Working with complex nested data structures
- Applying consistent business rules across transformations
- Mapping within a FHIR server
Consider alternatives when:
- Simple one-to-one field copying without validation needs
- One-time data migration where mapping rules won't be reused
- Performance is absolutely critical (direct Python code may be faster)
- Working with identical source and target structures
Basic Mapping Concepts
Mapping Structure
Every FHIR mapping consists of three main parts:
- Map Declaration: Defines the mapping's identity and metadata
- Uses Statements: Declare the structure definitions for source and target data
- Groups: Contain the actual transformation rules
Simple Field Mapping
The most basic transformation copies values directly from source fields to target fields:
from fhircraft.fhir.mapper import FHIRMapper
# Basic mapping structure
script = """
map 'http://example.org/simple-mapping' = 'SimpleMapping'
group main(source src, target tgt) {
// Direct field copying
src.name -> tgt.fullName;
src.age -> tgt.yearsOld;
src.email -> tgt.contactEmail;
}
"""
source_data = {"name": "John Doe", "age": 30, "email": "john@example.com"}
mapper = FHIRMapper()
targets, metadata = mapper.execute_mapping(script, source_data)
Working with FHIR Resources
Transform data into validated FHIR resources by specifying structure definitions:
# Mapping to FHIR Patient
script = """
map 'http://example.org/patient-mapping' = 'PatientMapping'
uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target
group main(source legacy, target patient: Patient) {
legacy.firstName -> patient.name.given;
legacy.lastName -> patient.name.family;
legacy.dob -> patient.birthDate;
legacy.sex where("$this = 'F'") -> patient.gender = 'female';
legacy.sex where("$this = 'M'") -> patient.gender = 'male';
}
"""
legacy_data = {
"firstName": "Alice",
"lastName": "Johnson",
"dob": "1985-03-15",
"sex": "F"
}
targets, _ = mapper.execute_mapping(script, legacy_data)
patient = targets[0] # Validated FHIR Patient resource
Value Transformations
Concatenation and String Operations
FHIR Mapper supports various string operations and value combinations. This example demonstrates how to combine multiple source fields and apply conditional logic during transformation:
script = """
map 'http://example.org/transform' = 'ValueTransform'
group main(source src, target tgt) {
// Combine fields
src.firstName + ' ' + src.lastName -> tgt.displayName;
// Conditional transformations
src.status where("$this = 'A'") -> tgt.active = true;
src.status where("$this = 'I'") -> tgt.active = false;
// Default values
src.priority -> tgt.priority "routine";
}
"""
source = {
"firstName": "John",
"lastName": "Doe",
"status": "A",
"priority": None
}
targets, _ = mapper.execute_mapping(script, source)
# Result: {"displayName": "John Doe", "active": True, "priority": "routine"}
Type Conversions and Formatting
Often you'll need to convert data types or reformat values during mapping. This example shows how to construct dates from separate components and convert between different data formats:
script = """
map 'http://example.org/convert' = 'TypeConvert'
group main(source src, target patient: Patient) {
// Date formatting
src.birthYear + '-' + src.birthMonth + '-' + src.birthDay -> patient.birthDate;
// Boolean conversions
src.isActive where("$this = 1") -> patient.active = true;
src.isActive where("$this = 0") -> patient.active = false;
// Code mappings
src.genderCode where("$this = 'M'") -> patient.gender = 'male';
src.genderCode where("$this = 'F'") -> patient.gender = 'female';
}
"""
Complex Mappings
Nested Object Transformations
FHIR resources often contain nested objects and complex structures. This example demonstrates how to map data into FHIR's hierarchical structure, creating nested objects as needed:
script = """
map 'http://example.org/nested' = 'NestedMapping'
group main(source src, target patient: Patient) {
// Map to nested structures
src.fullName -> patient.name.text;
src.firstName -> patient.name.given;
src.lastName -> patient.name.family;
// Transform collections
src.phoneNumbers -> patient.telecom as telecom then {
phoneNumbers.number -> telecom.value;
phoneNumbers.type -> telecom.system;
};
// Conditional nested mapping
src.emergencyContact where("exists()") -> patient.contact as contact then {
emergencyContact.name -> contact.name.text;
emergencyContact.relationship -> contact.relationship.text;
};
}
"""
source_data = {
"fullName": "Alice Johnson",
"firstName": "Alice",
"lastName": "Johnson",
"phoneNumbers": [
{"number": "555-0123", "type": "phone"},
{"number": "alice@email.com", "type": "email"}
],
"emergencyContact": {
"name": "Bob Johnson",
"relationship": "spouse"
}
}
Multiple Groups
Organize complex mappings using multiple transformation groups:
script = """
map 'http://example.org/multi-group' = 'MultiGroup'
group main(source src, target patient: Patient) {
src -> patient then demographics(src, patient);
src -> patient then contacts(src, patient);
src -> patient then identifiers(src, patient);
}
group demographics(source src, target patient: Patient) {
src.name -> patient.name.text;
src.birthDate -> patient.birthDate;
src.gender -> patient.gender;
}
group contacts(source src, target patient: Patient) {
src.phone -> patient.telecom as tel then {
phone -> tel.value;
'phone' -> tel.system;
};
src.email -> patient.telecom as email then {
email -> email.value;
'email' -> email.system;
};
}
group identifiers(source src, target patient: Patient) {
src.ssn -> patient.identifier as id then {
ssn -> id.value;
'http://hl7.org/fhir/sid/us-ssn' -> id.system;
};
}
"""
# Execute specific group
targets, _ = mapper.execute_mapping(script, source_data, group="demographics")
Multi-Source Transformations
Combine data from multiple sources into a single FHIR resource:
script = """
map 'http://example.org/multi-source' = 'MultiSource'
group main(source demographics, source insurance, target patient: Patient) {
// Data from demographics source
demographics.name -> patient.name.text;
demographics.birthDate -> patient.birthDate;
demographics.gender -> patient.gender;
// Data from insurance source
insurance.policyNumber -> patient.identifier.value;
insurance.carrier -> patient.identifier.system;
insurance.memberSince -> patient.identifier.period.start;
}
"""
# Multiple source data
demo_data = {
"name": "Alice Johnson",
"birthDate": "1985-03-15",
"gender": "female"
}
insurance_data = {
"policyNumber": "POL-12345",
"carrier": "ACME Insurance",
"memberSince": "2020-01-01"
}
# Pass sources as tuple
targets, _ = mapper.execute_mapping(
script,
(demo_data, insurance_data)
)
Loading Structure Maps
From Files and URLs
For complex mappings or reusable transformations, you can store mapping definitions in files or load them from remote URLs. This approach promotes maintainability and sharing of mapping logic:
# Load from JSON file
structure_map = mapper.load_structure_map("patient-mapping.json")
targets, _ = mapper.execute_mapping(structure_map, source_data)
# Load from URL
structure_map = mapper.load_structure_map(
"https://example.org/fhir/StructureMap/PatientMapping"
)
# Load from dictionary
map_definition = {
"resourceType": "StructureMap",
"status": "draft",
"name": "PatientMapping",
"url": "http://example.org/PatientMapping",
"group": [{
"name": "main",
"input": [
{"name": "src", "mode": "source"},
{"name": "tgt", "mode": "target", "type": "Patient"}
],
"rule": [{
"name": "name",
"source": [{"context": "src", "element": "name"}],
"target": [{"context": "tgt", "element": "name.text"}]
}]
}]
}
structure_map = mapper.load_structure_map(map_definition)
Working with Structure Definitions
Register custom structure definitions for complex mappings:
from fhircraft.fhir.resources.factory import construct_resource_model
# Define custom source structure
source_structure_def = {
"resourceType": "StructureDefinition",
"url": "http://example.org/StructureDefinition/LegacyPatient",
"name": "LegacyPatient",
# ... structure definition details
}
# Register with mapper
mapper.add_structure_definition(source_structure_def)
# Use in mapping script
script = """
map 'http://example.org/legacy' = 'LegacyMapping'
uses "http://example.org/StructureDefinition/LegacyPatient" alias LegacyPatient as source
uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target
group main(source legacy: LegacyPatient, target patient: Patient) {
legacy.fullName -> patient.name.text;
legacy.dateOfBirth -> patient.birthDate;
}
"""
Error Handling
Handle mapping failures gracefully:
from fhircraft.fhir.mapper.engine.exceptions import MappingError
try:
targets, metadata = mapper.execute_mapping(script, source_data)
patient = targets[0]
except MappingError as e:
print(f"Mapping failed: {e}")
print(f"Rule: {e.rule_path}")
print(f"Context: {e.context}")
except ValidationError as e:
print(f"FHIR validation failed: {e}")
Common Patterns
Data Cleaning During Transformation
Real-world data often requires cleaning and normalization. FHIR Mapper can perform data cleaning operations as part of the transformation process, ensuring clean, consistent output:
script = """
map 'http://example.org/clean' = 'DataCleaning'
group main(source src, target patient: Patient) {
// Clean phone numbers
src.phone.replace('-', '').replace(' ', '') -> patient.telecom.value;
// Standardize gender values
src.gender where("$this = 'M' or $this = 'Male' or $this = 'male')") -> patient.gender = 'male';
src.gender where("$this = 'F' or $this = 'Female' or $this = 'female')") -> patient.gender = 'female';
// Handle missing data with defaults
src.birthDate where("exists()") -> patient.birthDate;
src.active where("not exists()") -> patient.active = true;
}
"""
Conditional Resource Creation
Sometimes you only want to create resources when certain conditions are met. This pattern demonstrates conditional resource creation based on data availability and business rules:
script = """
map 'http://example.org/conditional' = 'ConditionalCreation'
group main(source src, target bundle: Bundle) {
// Create patient only if required fields exist
src where("(name.exists() and birthDate.exists())") -> bundle.entry as entry then {
src -> entry.resource = create('Patient') as patient then patient_details(src, patient);
};
}
group patient_details(source src, target patient: Patient) {
src.name -> patient.name.text;
src.birthDate -> patient.birthDate;
src.gender -> patient.gender;
}
"""
Further Help
- FHIR Mapping Tutorials - A set of tutorials covering the basics of the FHIR Mapping Language
- FHIR Mapping Language Specification - Current specification of the FHIR Mapping Language
What's Next?
Now that you understand FHIR transformation, explore these related topics:
- Resource Models - Understand the validation and structure of your mapped FHIR resources
- FHIRPath - Query and validate your transformed data using FHIRPath expressions
- Resource Factory - Load custom profiles and implementation guides for specialized mappings
- Pydantic FHIR - Understand the technical foundations that power automatic validation
For comprehensive workflows and integration patterns, see the User Guide overview.
Explore more: Back to User Guide Overview ←