Every year, the global FHIR community comes together for the “DevDays” event, where FHIR professionals from around the globe get together to swap ideas. This year, Daniel Mechanik, Our CTO and founder, gave a wonderful talk outlining how open-source FUME is already making a big difference in the conversion process from legacy systems to FHIR Resources.
We hope you enjoy!
After reading/watching, if you would like to know more about FUME, our unique FHIR-converter, follow this link
1. Video
Screenshot from video, Daniel Mechanik giving the talk at DevDays 2024
2. Key Takeaways – for people on the go!
- Converting data to FHIR is challenging due to complex profiles, URIs, binding slices, and terminology requirements—not just structure.
- State-dependent transformations require querying FHIR servers and considering differences between servers and versions over time.
- Current tools often lack comprehensive validation, rely heavily on hard-coded values, and can create vendor lock-in.
- A declarative, profile-driven approach to transformation, such as with the FHIR Utilized Mapping Engine (Fume), streamlines mapping, encourages central profile management, and improves data quality.
- Leveraging profiles, structured mappings, and extensible tooling makes complex FHIR integrations more maintainable and less error-prone—even for non-programmers.
3. Slides
4. Transcript
I have a lot of material in the decks. I will not be able to squeeze everything into 15 minutes, so at least you have the PDF. I will try to spontaneously go through what I feel is most important, and in the end, during the questions, you can make me go back to whatever I skipped.
“FHIR Conversion Made Easy”—hopefully that’s the goal. I want to show you some of the uses of an open-source tool we have developed in Outburn. I’m Daniel Mechanic—it’s actually “Mechanic,” but you can call me “a mechanic.” I’m the founder and CTO at Outburn LTD. I’m a data and integration architect, with some experience in healthtech, and I consider myself a FHIR evangelist. Let’s dive in.
Most importantly, this is Vicki (Daniel’s dog). Yes, she’s not only very beautiful, she’s also extremely smart. That was the top priority. Now, let’s go on.
The challenge is the conversion of data to FHIR, which is not as straightforward as it should be. It’s not about the data structure. The structure of FHIR might be by itself a bit complex, but that’s actually the easy part. The real challenge lies in the content: the correct use of URIs, understanding profiles, binding, slices, terminology, conversions. Each use case and profile has a lot of fixed values, so you find yourself hard-coding them into whatever tool you’re using.
If you have a resource repository to work with and you don’t want to destroy your data, then you’ll probably need to query that server before deciding what to do. That affects the conversion. The same source record will have different results if it’s a different FHIR server, and even if it’s the same server at different points in time. It’s not a stateless process. A converter cannot be stateless; it needs to take into account the state of the data on the server.
Existing tools, like plugins and SDKs, are usually platform- or language-specific, so they are not reusable across organizations—even if we are implementing exactly the same use case. They usually create vendor lock-in. Replacing the tools means we have to rewrite everything. The audience is often a programmer, so many of you here at DevDays seem to be programmers. But in the real world, the programmers have not necessarily been to DevDays and are not FHIR specialists. You might have data experts and FHIR experts who are not good programmers, or not programmers at all. You’ll also have very good programmers who are not data experts and not FHIR experts. You don’t want them making FHIR-related decisions, and they are expensive.
Regarding validation, the extent of validation in existing tools is usually limited. It varies between them. Often, it’s just base code validation or cardinality checks—usually only base, not slices or profiles. You usually realize you have data problems only in production, which leads to poor data quality.
Conformance to profiles is entirely up to the implementer. If the implementer knows how to read the profile and copy-paste correctly, it might be conformant. But a lot of times that doesn’t happen. It’s a process prone to errors and hard to maintain. If you have hard-coded URIs all over your code, and you need to change a URI—say, replace the URI of an extension—that can be very painful.
From the other side, existing FHIR data is not trivial to work with. Sometimes very simple questions require chains of API calls. You need to extract data from FHIR resources with some expertise, and that’s not trivial for everyone. Legacy systems may not be able to work directly with the FHIR APIs or the resources. Resolving references is usually part of the picture.
It’s a concept I call a “reverse facade.” It could be very useful to have a reverse facade for those systems to continue seeing the data as they are used to, without needing to become FHIR experts.
Not all FHIR servers are the same. If you want to be interoperable as a client, you should rely only on widely supported capabilities and you need to compensate for what’s missing somehow. I don’t know of a vendor-agnostic method to implement these missing capabilities. If you choose a server and need to implement a capability that doesn’t come out of the box, that will be server-specific and not interoperable.
Operation definitions and search parameters are not executable, they are just definitions. In the end, you need to code them into the product. FHIR-to-FHIR conversions are also something we see the need for, such as transforming a questionnaire response into observations, or moving from one FHIR version to another, or dealing with different profiles. And “Patch” can also become very complex when you want to add data to existing resources. As far as I know, the existing mechanisms are not perfect for that.
The vision is to have a uniform method for FHIR transformations that is production-ready and performant, easy to author and maintain even for non-programmers, and should be platform-agnostic. It should be declarative, so we focus on what we want to do and not on how. We need to maximize the use of FHIR assets like profiles, supporting them out of the box, and allow sharing and reuse of mappings through StructureMaps, ConceptMaps, and FHIR resources. It should be extensible, modular, and open source. One important thing: you should be able to call the transformation using the RESTful API.
This concept is inspired by the FHIR mapping language, but we ruled out the FHIR mapping language as a solution. It doesn’t feel production-ready; it’s more like an academic experiment. The function library is limited. There are no real supporting tools except for the Java validator. It’s not easy to author, the syntax is hard to learn. It’s not declarative; it’s more procedural in its nature. It does not maximize the use of FHIR profiles. You cannot assign a value into a slice, and you still need all those fixed values in the map. It’s also not very extensible.
Another option we ruled out is FHIR Shorthand which is something we were inspired by, because it does let you create instances of profiles and puts all the fixed values in for you, which is nice. But it’s not built for production—it’s built for profiling. Only static values can be used, and you need to put all the values inside the map. So it’s not a real solution, but it points in a good direction.
Now, introducing FUME (the FHIR Utilized Mapping Engine). It’s essentially a converter API factory. We took the FHIR Shorthand language as inspiration and added JSONata, an IBM open-source library for JSON data extraction and transformation. We combined them so you can create instances of profiles and assign values directly into slices, extensions, whatever you need. It validates on the go. The validation is not yet a full FHIR validator, but it’s going to be. You can do inline FHIR API calls, so you know the state of the resources on the server and can make decisions on the fly. You can convert terminologies using the “convert” function. The mappings are saved as StructureMaps, so you can share them and reuse them. We provide native support for JSON, CSV, and HL7 V2. Since it’s extensible, I will show you later that one implementer has already added support for XML, and I’m waiting for his PR to merge it into the library. It’s easily extended with additional functionalities—any JavaScript function you have can be added. You invoke the transformation using an API.
Let’s see some examples. Suppose I have a source with a patient MRN, a doctor ID, a date, systolic, and diastolic values. I need it to be conformant to the blood pressure profile inherited from the vital signs profile. The map is something like: instance of BP, the ID of the profile (it can be a URL). I put “final” in the status, effectiveDateTime equals the name of the field from the source. Subject reference uses a literal function that translates a FHIR search, like conditional reference into a literal reference. I’m looking for the patient with that MRN, and the same for the performer. For the component slice “Systolic BP,” I just put the systolic value in there, and the diastolic in the other. I don’t need to worry about valueQuantity since the profile defines a single data type.
The result is large, so I won’t show it all here. You get a lot of data: category from the parent profile, meta profile, the code, and then the component with the code and the valueQuantity with system, code, unit, and the value from my source. The references have been converted to literal references.
Another example: we have status “active,” an SSN identifier, a first name, last name, birth date, and sex with code “F.” You can use comments inside the map. The first thing I did was define an internal function to transform the date format. Then instanceOf: “Patient.” For the identifier, you can see the system uses an alias, just like in FHIR Shorthand, and that would become the URI for the SSN. For “active,” I use a Boolean: if status equals active, then true, otherwise false. For name.given and name.family, I don’t care that “given” is an array—it handles that. I call the date-transforming function I defined earlier, and I show how to translate “F” through a ConceptMap whose ID is “gender.” The result is a Patient resource with the SSN, active = true, the given name in an array, the family name as a string, the birth date transformed to the correct format, and the sex translated to female.
Another source: this is an example of string manipulations. This time, it’s an ILCore patient profile. I’m using the first row as a slice for the national identifier and padding that number with zeros using the pad function. The name, I used the concatenation function. For gender, I put in an extension—this is the syntax for that. You don’t need to worry about the URL, just the ID of the data-absent-reason. For address, I show an example of context usage. I take the context of the source object “address,” and inside that I have the fields, so I can just use them directly. Another extension is put on the city, which is a primitive. For phones, which is an array, using it as a context just injects it into the array. The result is long, but it shows the identifier padded, the slice has a fixed value for the system, the name has been concatenated. This trick in the JSON format, where you need to put an underscore, it has been done for you. the complex extension for latitude and longitude, and the city with an underscore because it’s a primitive and we have an extension on that, and the telecom array – so as many as we have in the source, that’s how many we get in the target.
A reverse facade example: I have a system that wants to get a practitioner’s information using the license number. This is what the map would look like. I do a search, take the first entry, and it extracts fields from that resource. The system can continue working with its own interfaces even though it’s a FHIR server underneath.
I’ll skip some examples; you can look at them later.
Some large-scale, real-life test cases: The Ministry of Health in Israel is using it to populate their National FHIR server with public health data. They extended it with support for XML. Rambam Medical Center is populating a FHIR server from their proprietary EMR data and has defined their own profiles. Laniado Medical Center is doing the same. Reverse facade implementations: I know of a few. A FHIR interceptor is also something people try, using this mapping language to create interceptors that add capabilities. This could help create operation definitions that are shareable. For data quality assurance, I know someone who periodically fetches bulk data using the mapping language and checks it against rules.
The most important thing is that this encourages organizations to create their own profiles. That way, all of their URIs and such are managed in a central place—the profile. The mappings deal only with the dynamic data, while all the static stuff is in the profiles. I believe this is a very good practice and it improves data quality.
I think we’re out of time. It’s time for questions and answers.
“Have you thought about a Visual Studio Code extension?”
“Yes, we’ve only thought about it. Haven’t done it yet, but it’s on the roadmap.”
“Anyone else? This might be a broader question: does anyone have experience in institutionalizing the process of figuring out mappings? Who decides that? As a developer, I can guess, but I want input from customers. I want a system or an internal task force. Any recommendations would be appreciated.”
“In our experience, the business analysts take responsibility for the logic, the mappings, and the profiling. They tell the developers, ‘For this source, call this API,’ and that’s it. The responsibility is split. The developers just move data from here to the API and then into the FHIR server. They don’t deal with the logic between. The business analysts and data experts update the mappings when needed. It’s also easier to debug, since they understand what’s happening.”
“Is the workflow any different with gigabytes of data, or do you still use the REST API?”
“Yes, it would differ. Since it’s over a REST API, it’s beneficial not to send gigabytes of data in a single interaction. You should split it—if it’s a CSV, do a thousand or a hundred thousand rows at a time. It depends on the infrastructure. If the server is strong enough, it can handle whatever you want. Just remember it’s over HTTP.”
“Does the tool handle the connection information to servers, or is that external?”
“For now, there’s a configuration for a single server connection. You configure all the connection details, and that’s the server it works against. You’re not exposed to this from the API, except if you do a search and see the full URL.”