# C-CDA to FHIR conversion

Turn C-CDA clinical documents - CCDs, discharge summaries, referral notes - into FHIR R4 bundles with one call, however the documents reach you.

A C-CDA document is an XML patient summary - a Continuity of Care Document (CCD), a discharge summary, a referral note - that EHRs, health information exchanges and Direct messaging send when a patient moves between providers. Zato converts such a document into a FHIR R4 bundle with one call, so the problems, medications, allergies and results it holds are read the same way as data from a FHIR server, and nothing in your code touches the XML:

```python
bundle = self.ccda.to_fhir(document)
```

The call takes the document as text or bytes, exactly as it arrived, and returns a typed bundle - a Patient from the document's header, a Condition for each problem, a MedicationStatement for each medication, an AllergyIntolerance for each allergy, an Observation for each result and vital sign, and so on for every clinical section, with the references between the resources already in place.

## A complete service {#a-complete-service}

A service is a Python class that Zato runs when something invokes it. The service below receives a CCD over REST, converts it and stores the result in a FHIR server through an [outgoing FHIR connection](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html):

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class StoreCCD(Service):
    """ Receives a C-CDA document and stores its contents in a FHIR server.
    """
    name = 'demo.ccda.store'

    def handle(self) -> 'None':

        # The document, as it arrived ..
        document = self.request.input

        # .. one call converts it to a FHIR transaction bundle ..
        bundle = self.ccda.to_fhir(document)

        # .. and the bundle goes to the FHIR server as a whole.
        client = self.fhir['FHIR.Registry']
        response = client.execute('', method='post', data=bundle.to_dict())

        self.logger.info('Stored %s resources -> %s', len(bundle.entry), response['type'])
```

The bundle is a transaction, so the server stores all of its resources together or none of them, and [sending bundles](https://zato.io/docs/dev/healthcare/hl7/to-fhir/sending/index.html) explains how to read the server's reply.

## What comes out of a document {#what-comes-out-of-a-document}

Each section of the document becomes resources of its own kind. A CCD with the usual sections produces:

```python
bundle = self.ccda.to_fhir(document)

for entry in bundle.entry:
    self.logger.info(entry.resource.resource_type)
```

```text
Composition
Patient
Practitioner
Organization
Condition
Condition
MedicationStatement
AllergyIntolerance
Immunization
Observation
Observation
Procedure
Encounter
DocumentReference
```

| Section | Resources |
| --- | --- |
| Document header | Patient, the authoring Practitioner and Organization, and a Composition listing the sections |
| Problems | A Condition per problem, with its SNOMED or ICD-10 code, status and onset |
| Medications | A MedicationStatement per medication, with its RxNorm code, dose and route |
| Allergies | An AllergyIntolerance per allergy, with the substance and the reactions |
| Immunizations | An Immunization per vaccine given, with its CVX code and date |
| Results | An Observation per laboratory result, with its LOINC code, value and reference range, grouped under a DiagnosticReport |
| Vital signs | An Observation per measurement - blood pressure, heart rate, weight, and the rest |
| Procedures | A Procedure per procedure performed |
| Encounters | An Encounter per visit, with its dates and location |
| Social history | An Observation per entry - smoking status among them |
| Plan of treatment | A CarePlan with the planned activities |
| The document itself | A DocumentReference holding the original XML, so the whole document is kept alongside the resources made from it |

The conversion covers the standard document types of the C-CDA specification - the CCD, Consultation Note, Discharge Summary, History and Physical, Operative Note, Procedure Note, Progress Note, Referral Note and Transfer Summary - and reads the standard sections wherever they appear in any of them.

A section the conversion does not know, one a vendor added of its own, produces no resources, and the human-readable text that every section carries is not turned into data either. Both stay in the document, which is why the DocumentReference keeps it in full - a service that needs them reads them from there.

Resources that need an ID receive one that is the same each time the same document is converted, so converting a document twice does not store its contents twice.

## Read the result {#read-the-result}

The bundle's resources are typed, with attribute access to every field, and the [path access](https://zato.io/docs/dev/healthcare/hl7/fhir/path-access/index.html) of any FHIR resource works on them:

```python
bundle = self.ccda.to_fhir(document)

for entry in bundle.entry:
    resource = entry.resource

    if resource.resource_type == 'Condition':
        self.logger.info('Problem -> %s', resource.code.text)

    elif resource.resource_type == 'MedicationStatement':
        self.logger.info('Medication -> %s', resource.medicationCodeableConcept.text)
```

When plain data is wanted instead, `bundle.to_dict()` and `bundle.to_json()` return the bundle as a dictionary and as a JSON string, each in the shape FHIR servers accept.

## Where documents come from {#where-documents-come-from}

The document reaches the service in one of several ways, and in each the service hands what it received to the one call:

1. **A REST channel.** A channel is an endpoint that invokes a service for each request it receives. An HIE or an EHR posts the document to it, and `self.request.input` is the document.
2. **A file.** A [file transfer](https://zato.io/docs/dev/file-transfer/receiving-files.html) channel watches a directory, an SFTP server or an inbox that an EHR exports documents to, and `self.request.input.data` is the document.
3. **A FHIR server.** Epic, Oracle Health and other EHRs publish each document as a DocumentReference pointing at a Binary resource that holds the XML - the next section shows how to read it.
4. **An HL7 v2 message.** An `MDM^T02` message carries the document in its `OBX-5` field, base64-encoded, and the [MLLP channel](https://zato.io/docs/dev/healthcare/hl7v2/mllp/index.html) delivers the parsed message to the service.
5. **Direct messaging.** The document is an attachment of a [Direct message](https://zato.io/docs/dev/healthcare/direct/index.html) that an IMAP connection to your HISP reads, on its own or inside an [IHE XDM package](https://zato.io/docs/dev/healthcare/xdm/index.html), and `self.request.input.documents()` returns it either way.

The same call converts the document in every one of them.

## Convert a document from a FHIR server {#convert-a-document-from-a-fhir-server}

An EHR that exposes its documents over FHIR lists them as DocumentReference resources, one per document, and each points at a Binary resource holding the document itself. Reading the Binary and converting it is three steps:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class ImportSummaries(Service):
    """ Reads a patient's clinical summaries from an EHR and stores them in the registry.
    """
    name = 'demo.ccda.import-summaries'

    def handle(self) -> 'None':

        source = self.fhir['FHIR.Epic']
        registry = self.fhir['FHIR.Registry']

        # Each DocumentReference describes one document of the patient ..
        references = source.resources('DocumentReference').search(patient='erXuFYUfucBZaryVksYEcMg3').fetch_all()

        for reference in references:

            # .. its content points at the Binary that holds the document ..
            binary_url = reference['content'][0]['attachment']['url']
            binary = source.get('Binary', binary_url.split('/')[-1])

            # .. and the Binary's data is the document, which one call converts.
            bundle = self.ccda.to_fhir(binary['data'])

            registry.execute('', method='post', data=bundle.to_dict())
```

A Binary's `data` is base64 text and `to_fhir` accepts it as it is, decoding it before the conversion, so there is no step between the read and the call.

## Convert on arrival {#convert-on-arrival}

An HL7 REST channel whose version is **C-CDA** converts each document before the service runs, so the service receives the bundle itself and there is no call at all:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class StoreCCD(Service):
    """ Receives a C-CDA document that the channel has already converted.
    """
    name = 'demo.ccda.store'

    def handle(self) -> 'None':

        bundle = self.request.input

        client = self.fhir['FHIR.Registry']
        client.execute('', method='post', data=bundle.to_dict())
```

A document the channel cannot read - one that is not XML, or not a CDA document - is rejected with an HTTP 400 response before the service is invoked.

The same channel in an [enmasse](https://zato.io/docs/admin/enmasse.html) file is a REST channel whose `data_format` is `hl7-ccda`:

```yaml
channel_rest:
  - name: ccda.store
    service: demo.ccda.store
    url_path: /ccda/store
    data_format: hl7-ccda
```

## Troubleshooting {#troubleshooting}

**The bundle has a Patient and little else.** The document's sections carry text but no structured entries, which is how some systems export summaries. The text is in the DocumentReference and in the Composition's sections, and there is no data to make resources from.

**A section of the document produced no resources.** It is one the conversion does not know, usually a vendor's own, and its contents are kept in the DocumentReference.

**The server rejected the bundle.** The server's reply names the resource and the field it refused - typically a code system the server does not accept or a required field the document did not fill in. Validating the bundle's resources before sending, as [sending bundles](https://zato.io/docs/dev/healthcare/hl7/to-fhir/sending/index.html) shows, finds the field in your own service first.

**The call raised an exception.** The input was not a CDA document - the usual cause is a document passed in still wrapped in the message or the envelope it arrived in, such as the whole `MDM^T02` message rather than the contents of its `OBX-5` field.

**The attachment is a zip.** It is an [IHE XDM package](https://zato.io/docs/dev/healthcare/xdm/index.html), and the attachment's `documents()` returns the documents inside it, each ready for the call.

## See also {#see-also}

- [HL7 v2 to FHIR](https://zato.io/docs/dev/healthcare/hl7/to-fhir/index.html) - The same one-call conversion for HL7 v2 messages
- [Sending bundles](https://zato.io/docs/dev/healthcare/hl7/to-fhir/sending/index.html) - Posting a bundle to a FHIR server and reading the reply
- [FHIR connections](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html) - The outgoing connection that stores the bundle
- [Resources](https://zato.io/docs/dev/healthcare/hl7/fhir/resources/index.html) - Reading and writing the resources a document becomes
- [Receiving files](https://zato.io/docs/dev/file-transfer/receiving-files.html) - Picking documents up from directories, SFTP and inboxes

## Learn more {#learn-more}

- [Healthcare interface engine](https://zato.io/docs/dev/healthcare/) - Clinical messages, FHIR, conversion and operations
- [HL7 v2 parsing](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/) - Messages as typed objects, with field access and validation
- [MLLP channels](https://zato.io/docs/dev/healthcare/hl7v2/mllp/) - Receiving and sending HL7 v2 over MLLP sockets
- [FHIR integrations](https://zato.io/docs/dev/healthcare/hl7/fhir/) - Read and write FHIR resources, with paths, bundles and extensions
- [HL7 v2 to FHIR](https://zato.io/docs/dev/healthcare/hl7/to-fhir/) - Converting v2 messages into FHIR bundles, codes and references included
- [EDIFACT](https://zato.io/docs/dev/healthcare/edifact/) - Parsing interchanges, dialects and the transports they arrive on
- [Transformation](https://zato.io/docs/dev/healthcare/transformation/) - One message in, another standard out, in ordinary Python
- [Audit log](https://zato.io/docs/dev/healthcare/audit-log.html) - Every message with its acknowledgment, searchable by patient identifier
- [AI in clinical interfaces](https://zato.io/docs/dev/healthcare/ai/) - Services calling LLMs and AI agents calling clinical services as tools
