IHE XDM document packages
Read IHE XDM packages - the zip with IHE_XDM and METADATA.XML that Direct messages, SFTP drops and media carry clinical documents in - and get each document with its metadata, in Python.
An IHE XDM package - Cross-Enterprise Document Media Interchange - is a zip archive that carries clinical documents together with a description of each of them, which is how documents travel when there is no document registry between the sender and the recipient - in a Direct message, on a USB stick or a CD handed to a patient, or as a file dropped on an SFTP server. Zato opens the package and gives your service each document the package describes, with its metadata, in one call.
Inside the zip, an IHE_XDM directory holds one subdirectory per submission set - the documents one sender sent together - and each subdirectory holds the documents and a METADATA.XML file, an ebXML SubmitObjectsRequest with an entry per document stating its location, MIME type, title, patient, document codes, hash and size. Next to the directory, the root of the zip has an INDEX.HTM and a README.TXT for a person opening the package by hand, which the reading skips.
Read a package
A service is a Python class that Zato runs when something invokes it. Whatever brought the package to the service - an email attachment, a file, a REST request - the service reads the documents inside it the same way:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class XDMImport(Service):
""" Stores each clinical document of an XDM package in a FHIR server.
"""
name = 'documents.xdm-import'
def handle(self) -> 'None':
# Each document the package's metadata names, in metadata order ..
for document in self.request.input.documents():
self.logger.info('%s -> %s, %s, patient %s', document.file_name, document.title, document.mime_type,
document.patient_id)
# .. the human-readable renderings that travel alongside are not converted ..
if document.mime_type == 'application/pdf':
continue
# .. and each clinical document becomes a FHIR bundle that goes to the server as a whole.
bundle = self.ccda.to_fhir(document.data)
client = self.fhir['FHIR.Registry']
client.execute('', method='post', data=bundle.to_dict())
Each document has these attributes:
| Attribute | What it holds |
|---|---|
data | The document's bytes, as they are in the package |
file_name | The document's file name inside the package |
mime_type | Its MIME type, from the metadata |
size | The size of the document, in bytes |
source | xdm for a document read out of a package, zip for a file of a plain archive and attachment for a document that arrived on its own |
title | The document's title |
patient_id | The patient the document is about, as the sender's system identifies them |
class_code | The document's class code, e.g. 34133-9 for a summary of episode note |
type_code | The document's type code |
unique_id | The document's unique ID, the same wherever the document goes |
creation_time | When the document was created, as the metadata states it |
language | The document's language code, e.g. en-US |
The call opens the package, reads the metadata of every submission set and returns the documents the metadata names - a file in the package that no entry describes, a thumbnail or a note, is left out. Each document's size and SHA-1 hash are checked against what the metadata states about it, and a document that does not match raises an exception before anything is returned, so a service never acts on a package that was changed in transit.
The call is not specific to XDM. An attachment or a file that is not a zip is returned as the one document it is, with its MIME type and no metadata, and a zip that has no IHE_XDM directory returns each file inside it as a document with source set to zip - so a service written as the one above handles a bare C-CDA, a plain zip of documents and an XDM package without telling them apart.
Where packages come from
The package reaches the service in one of several ways, and in each the service calls documents() on what it received:
- A Direct message. The package is the attachment of an email, and an IMAP connection with its Scheduler section set to Each attachment invokes the service once per attachment, so
self.request.inputis the attachment. A message whose subject starts withXDM/1.0/DDMcarries a package. - A file. A file transfer channel watches a directory or an SFTP server that a sender drops packages into, and
self.request.inputis the file. - A REST upload. A channel receives the zip in the request body, and
read_documentsfromzato.common.documents.unpackreads it:
# -*- coding: utf-8 -*-
# Zato
from zato.common.documents.unpack import read_documents
from zato.server.service import Service
class XDMUpload(Service):
""" Receives an XDM package over REST and logs the documents it names.
"""
name = 'documents.xdm-upload'
def handle(self) -> 'None':
for document in read_documents(self.request.input, file_name='upload.zip'):
self.logger.info('%s -> %s', document.file_name, document.title)
For the first case, an IMAP connection reading the inbox that packages arrive in is created under Connections > Outgoing > E-mail > IMAP in the Dashboard, with the service to invoke and Each attachment in its Scheduler row:
Metadata
Each document's attributes come from its entry in METADATA.XML - an ExtrinsicObject with slots, a name, classifications and external identifiers:
| Attribute | Where it comes from |
|---|---|
file_name | The URI slot, which names the file relative to the metadata, with the submission set's directory |
mime_type | The mimeType attribute of the entry |
title | The Name element's LocalizedString |
patient_id | The XDSDocumentEntry.patientId external identifier |
class_code | The classCode classification, scheme urn:uuid:41a5887f-8865-4c09-adf7-e362475b143a |
type_code | The typeCode classification, scheme urn:uuid:f0306f51-975f-434e-a61c-c59651d33983 |
unique_id | The XDSDocumentEntry.uniqueId external identifier |
creation_time | The creationTime slot |
language | The languageCode slot |
size | Counted from the document, and compared with the size slot when the metadata has one |
| Hash | The hash slot, compared with the SHA-1 of the document when the metadata has one |
A package may hold several submission sets, each in its own subdirectory with its own metadata, and the documents of all of them are returned, set by set.
XDR and XDS.b
The same metadata describes documents in the other IHE document-sharing profiles. XDR - Cross-Enterprise Document Reliable Interchange - sends the documents and their SubmitObjectsRequest in a SOAP message rather than a zip, and XDS.b registers them with a document registry that other systems query. Both are SOAP 1.2 with MTOM and the stack that IHE healthcare document exchange describes, and XDM is the same documents and metadata with no SOAP and no registry, which is why it is what travels in email and on media.
Troubleshooting
The exception says no-metadata. The zip has an IHE_XDM directory but no METADATA.XML in any of its subdirectories, or the file is there but is not XML the reading can parse. The sender's package is incomplete.
The exception says missing-file. An entry's URI slot names a file that is not in the package. The exception's file_name is the name the metadata gave.
The exception says hash-mismatch or size-mismatch. The document in the package is not the one the metadata describes - it was changed after the package was built, or the sender's system states a hash or size for a different version of the document. The message or the file stays where it was, since the service raised, and the sender is the one to ask.
All files came back as documents, with no titles. The zip has no IHE_XDM directory at its root, so it was read as a plain archive. A package whose IHE_XDM is inside another directory is not an XDM package by the profile's definition.
See also
| Page | What it covers |
|---|---|
| Direct Secure Messaging | The messages that most XDM packages arrive in |
| C-CDA to FHIR | Converting the documents a package holds |
| Receiving files | Picking packages up from directories and SFTP servers |
| IHE healthcare document exchange | XDR and XDS.b over SOAP, with MTOM and signed SAML |
| IMAP | Reading email on a schedule, with messages or attachments on input |
Learn more
Schedule a meaningful demo
Book a demo with an expert who will help you build meaningful systems that match your ambitions
"We evaluated 12 integration platforms and Zato was the only one to score 100%."
Philip Zuñiga, Assistant Professor, University of the Philippines