POC - Mapping CDA to FHIR
0.1.0 - ci-build
POC - Mapping CDA to FHIR - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
| Official URL: https://interop.esante.gouv.fr/ig/fhir/mappingcdafhir/StructureMap/CdaToBundle | Version: 0.1.0 | |||
| Draft as of 2025-11-07 | Computable Name: CdaToBundle | |||
Mapping de CDA vers FHIR Bundle (A partir des sources de Oliver Egger)
map "https://interop.esante.gouv.fr/ig/fhir/mappingcdafhir/StructureMap/CdaToBundle" = "CdaToBundle" // Mapping de CDA vers FHIR Bundle (A partir des sources de Oliver Egger) uses "http://hl7.org/cda/stds/core/StructureDefinition/ClinicalDocument" alias ClinicalDocument as source uses "http://hl7.org/cda/stds/core/StructureDefinition/AssignedAuthor" alias AssignedAuthor as source uses "http://hl7.org/cda/stds/core/StructureDefinition/AssignedEntity" alias AssignedEntity as source uses "http://hl7.org/cda/stds/core/StructureDefinition/CustodianOrganization" alias CustodianOrganization as source uses "http://hl7.org/cda/stds/core/StructureDefinition/Section" alias Section as source uses "http://hl7.org/cda/stds/core/StructureDefinition/PatientRole" alias PatientRole as source uses "http://hl7.org/fhir/StructureDefinition/Bundle" alias Bundle as target uses "http://hl7.org/fhir/StructureDefinition/Composition" alias Composition as target uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target uses "http://hl7.org/fhir/StructureDefinition/Person" alias Person as target uses "http://hl7.org/fhir/StructureDefinition/Practitioner" alias Practitioner as target uses "http://hl7.org/fhir/StructureDefinition/Organization" alias Organization as target imports "https://interop.esante.gouv.fr/ig/fhir/mappingcdafhir/StructureMap/CdaToFHIRTypes" // _________________________ Document Level Template _________________________ group CdaToBundle(source cda : ClinicalDocument, target bundle : Bundle) { cda -> bundle.entry as e, e.resource = create('Composition') as composition, composition.id = uuid() as uuid, e.fullUrl = append('urn:uuid:', uuid), bundle.entry as e2, e2.resource = create('Patient') as patient, patient.id = uuid() as uuid2, e2.fullUrl = append('urn:uuid:', uuid2) then { cda then ClinicalDocumentToBundle(cda, patient, composition, bundle) "cdatobundle"; } "ClinicalDocumentToBody"; } group ClinicalDocumentToBundle(source cda : ClinicalDocument, target patient : Patient, target composition : Composition, target bundle : Bundle) { cda -> bundle.id = uuid() "id"; cda.id as cdaId -> bundle.identifier as identifier then { cdaId.root as root where cdaId.extension.exists() -> identifier.system = translate(root, '#oid2uri', 'uri') "system"; cdaId.extension as extension -> identifier.value = extension "value"; cdaId.root as root where cdaId.extension.empty() -> identifier.system = 'urn:ietf:rfc:3986' "systemOid"; cdaId.root as root where cdaId.extension.empty() -> identifier.value = append('urn:oid:', root) "valueOid"; } "identifier"; cda -> bundle.type = 'document' "type"; cda.effectiveTime as effectiveTime -> bundle.timestamp = create('instant') as timestamp then TSInstant(effectiveTime, timestamp) "timestamp"; cda then ClinicalDocumentComposition(cda, composition, patient, bundle) "composition"; cda.component as cdaComponent then { cdaComponent.structuredBody as body then { body.component as bodyComponent then { bodyComponent.section as srcSection where $this.code.empty() -> composition.section as tgtSection then ClinicalDocumentSection(cda, srcSection, patient, tgtSection, bundle); } "bodyComponent"; } "body"; } "cdaComponent"; } // // _________________________ Section Level Templates _________________________ group ClinicalDocumentSection(source cda : ClinicalDocument, source src : Section, source patient : Patient, target tgt, target bundle : Bundle) { src.title as t -> tgt.title = (t.xmlText); src.code as srcCode -> tgt.code = srcCode; src.text as cdaText -> tgt.text as fhirText then { cdaText -> fhirText.status = 'generated' "narrativeStatus"; cdaText as t -> fhirText.div = t "narrativeText"; } "cdaText"; src.section as srcSection -> tgt.section as tgtSection then ClinicalDocumentSection(cda, srcSection, patient, tgtSection, bundle); } // _________________________ Entry Level Templates ________________________ // _________________________ Header Level Templates _________________________ group ClinicalDocumentComposition(source src : ClinicalDocument, target tgt : Composition, target patientResource : Patient, target bundle : Bundle) { src.languageCode as languageCode -> tgt.language = create('code') as code then CSCode(languageCode, code); src.id as id where src.setId.exists().not() -> tgt.identifier = create('Identifier') as identifier then II(id, identifier) "identifier"; src.setId as setId -> tgt.identifier = create('Identifier') as identifier then II(setId, identifier) "setIdentifier"; src -> tgt.status = 'final' "status"; src.code as srcCode -> tgt.type = create('CodeableConcept') as cc then CDCodeableConcept(srcCode, cc); src.title as t -> tgt.title = (t.xmlText); src.recordTarget as recordTarget then { recordTarget.patientRole as patient -> tgt.subject = create('Reference') as reference, reference.reference = ('urn:uuid:' + %patientResource.id) then ClinicalDocumentPatientRole(patient, patientResource, bundle) "subject"; } "patientRole"; src.componentOf as comp -> bundle.entry as e, e.resource = create('Encounter') as encounter, encounter.id = uuid() as uuid, e.fullUrl = append('urn:uuid:', uuid) then { comp.encompassingEncounter as srcEnc -> tgt.encounter = create('Reference') as reference, reference.reference = ('urn:uuid:' + %encounter.id) then ClinicalDocumentEncounter(srcEnc, bundle, encounter) "srcEncounter"; } "encompassingEncounter"; src.effectiveTime as effectiveTime -> tgt.date = create('dateTime') as date then TSDateTime(effectiveTime, date) "compositionDate"; src.author as srcAuthor -> bundle.entry as e, e.resource = create('Practitioner') as practitioner, practitioner.id = uuid() as uuid2, e.fullUrl = append('urn:uuid:', uuid2), bundle.entry as eRole, eRole.resource = create('PractitionerRole') as practitionerRole, practitionerRole.id = uuid() as uuidRole, eRole.fullUrl = append('urn:uuid:', uuidRole), tgt.author = create('Reference') as reference, reference.reference = ('urn:uuid:' + %practitionerRole.id) then { srcAuthor -> practitioner.meta = create('Meta') as meta then { srcAuthor -> meta.profile = 'https://interop.esante.gouv.fr/ig/fhir/annuaire/StructureDefinition/as-practitioner' "practitionerProfile"; } "practitionerMeta"; srcAuthor -> practitionerRole.meta = create('Meta') as metaRole then { srcAuthor -> metaRole.profile = 'https://interop.esante.gouv.fr/ig/fhir/annuaire/StructureDefinition/as-practitionerrole' "practitionerRoleProfile"; } "practitionerRoleMeta"; srcAuthor -> practitionerRole.practitioner = create('Reference') as refPract, refPract.reference = ('urn:uuid:' + %practitioner.id) "practitionerRef"; srcAuthor.assignedAuthor as assignedAuthor then { assignedAuthor.id as id -> practitioner.identifier = create('Identifier') as identifier then { id -> identifier then II(id, identifier) "baseIdentifier"; id where root = '1.2.250.1.71.4.2.1' -> identifier.type = create('CodeableConcept') as type then { id -> type.coding = create('Coding') as coding then { id -> coding.system = 'https://hl7.fr/ig/fhir/core/CodeSystem/fr-core-cs-v2-0203' "system"; id -> coding.code = 'IDNPS' "code"; } "coding"; } "idNatPs"; } "identifier"; assignedAuthor.addr as addr -> practitioner.address = create('Address') as address then ADAddress(addr, address); assignedAuthor.telecom as tlc -> practitioner.telecom = create('ContactPoint') as contactPoint then TELContactPoint(tlc, contactPoint); assignedAuthor.assignedPerson as assPerson then { assPerson.name as pName -> practitioner.name = create('HumanName') as humanName then ENHumanName(pName, humanName); } "name"; assignedAuthor.code as roleCode -> practitionerRole.code = create('CodeableConcept') as cc then CDCodeableConcept(roleCode, cc) "roleCode"; assignedAuthor.code as roleCode where code.contains('/') -> practitioner.qualification as qualification then { roleCode -> qualification.code = create('CodeableConcept') as qualCode then { roleCode.code as fullCode -> qualCode.coding = create('Coding') as coding then { fullCode -> coding.code = (%fullCode.substring(%fullCode.indexOf('/') + 1)) "savoirFaireCode"; fullCode -> coding.system = 'https://mos.esante.gouv.fr/NOS/TRE_R38-SpecialiteOrdinale/FHIR/TRE-R38-SpecialiteOrdinale' "systemSavoirFaire"; roleCode.displayName as display -> coding.display = display "display"; } "coding"; } "qualCode"; } "savoirFaire"; assignedAuthor.code as roleCode where code.contains('/') and code.contains('_') -> practitioner.qualification as qualification then { roleCode -> qualification.code = create('CodeableConcept') as qualCode then { roleCode.code as fullCode -> qualCode.coding = create('Coding') as coding then { fullCode -> coding.code = (%fullCode.substring(%fullCode.indexOf('_') + 1).substring(0, %fullCode.substring(%fullCode.indexOf('_') + 1).indexOf('/'))) "professionCode"; fullCode -> coding.system = 'https://mos.esante.gouv.fr/NOS/TRE_G15-ProfessionSante/FHIR/TRE-G15-ProfessionSante' "systemProfession"; fullCode where %fullCode.substring(%fullCode.indexOf('_') + 1).substring(0, %fullCode.substring(%fullCode.indexOf('_') + 1).indexOf('/')) = '10' -> coding.display = 'Médecin' "displayMedecin"; } "coding"; } "qualCode"; } "profession"; assignedAuthor.representedOrganization as srcOrg -> bundle.entry as e2, e2.resource = create('Organization') as organization, organization.id = uuid() as uuid3, e2.fullUrl = append('urn:uuid:', uuid3), practitionerRole.organization = create('Reference') as refOrg, refOrg.reference = ('urn:uuid:' + %organization.id) then ClinicalDocumentOrganization(srcOrg, organization); } "author"; } "srcAuthor"; src.informant as srcInformant -> bundle.entry as e, e.resource = create('RelatedPerson') as relatedPerson, relatedPerson.id = uuid() as uuidRelated, e.fullUrl = append('urn:uuid:', uuidRelated) then { srcInformant.relatedEntity as relatedEntity then { relatedEntity -> relatedPerson.patient = create('Reference') as patientRef, patientRef.reference = ('urn:uuid:' + %patientResource.id) "patientRef"; relatedEntity.code as relationCode -> relatedPerson.relationship = create('CodeableConcept') as cc then CDCodeableConcept(relationCode, cc) "relationship"; relatedEntity.addr as addr -> relatedPerson.address = create('Address') as address then ADAddress(addr, address) "address"; relatedEntity.telecom as tlc -> relatedPerson.telecom = create('ContactPoint') as contactPoint then TELContactPoint(tlc, contactPoint); relatedEntity.relatedPerson as relPerson then { relPerson.name as pName -> relatedPerson.name = create('HumanName') as humanName then ENHumanName(pName, humanName); } "relatedPersonName"; }; }; src.confidentialityCode as confCode -> tgt.confidentiality = create('code') as code then CSCode(confCode, code); src.legalAuthenticator as legalAuth -> bundle.entry as e, e.resource = create('Practitioner') as practitioner, practitioner.id = uuid() as uuid2, e.fullUrl = append('urn:uuid:', uuid2) then { legalAuth -> tgt.attester as attester then { legalAuth -> attester.mode = 'legal' "mode"; legalAuth.time as time -> attester.time = create('dateTime') as dt then TSDateTime(time, dt); legalAuth.assignedEntity as entity -> attester.party = create('Reference') as reference, reference.reference = ('urn:uuid:' + %practitioner.id) then ClinicalDocumentEntityPractitioner(entity, practitioner) "entity"; } "attester"; } "legalAuth"; src.authenticator as auth -> bundle.entry as e, e.resource = create('Practitioner') as practitioner, practitioner.id = uuid() as uuid2, e.fullUrl = append('urn:uuid:', uuid2) then { auth -> tgt.attester as attester then { auth -> attester.mode = 'official' "mode"; auth.time as time -> attester.time = create('dateTime') as dt then TSDateTime(time, dt); auth.assignedEntity as entity -> attester.party = create('Reference') as reference, reference.reference = ('urn:uuid:' + %practitioner.id) then ClinicalDocumentEntityPractitioner(entity, practitioner) "entity"; } "attester"; } "auth"; src.custodian as custodian -> bundle.entry as e then { custodian.assignedCustodian as assignedCustodian -> e.resource = create('Organization') as organization, organization.id = uuid() as uuid3, e.fullUrl = append('urn:uuid:', uuid3) then { assignedCustodian.representedCustodianOrganization as srcOrg -> tgt.custodian = create('Reference') as reference, reference.reference = ('urn:uuid:' + %organization.id) then ClinicalDocumentOrganization(srcOrg, organization) "assignedCustodian"; } "custodian"; } "cust"; src.documentationOf as docOf then { docOf.serviceEvent as serviceEvent -> tgt.event as event then { serviceEvent.code as eventCode -> event.code = create('CodeableConcept') as cc then CDCodeableConcept(eventCode, cc) "eventCode"; serviceEvent.effectiveTime as effectivetime -> event.period = create('Period') as period then IVLTSPeriod(effectivetime, period) "eventTime"; // performerType: for src.performer.typeCode ... } "docOf"; }; src.relatedDocument as relatedDoc -> tgt.relatesTo as relates then { relatedDoc.typeCode as typeCode -> relates.code = typeCode; relatedDoc.parentDocument as parentDoc then { parentDoc.setId as sid -> relates.targetIdentifier = sid; } "parentDoc"; } "relatedDoc"; } group ClinicalDocumentEntityPractitioner(source src : AssignedEntity, target tgt : Practitioner) { src -> tgt.meta = create('Meta') as meta then { src -> meta.profile = 'https://interop.esante.gouv.fr/ig/fhir/annuaire/StructureDefinition/as-practitioner' "profile"; } "meta"; src.id as srcId -> tgt.identifier = create('Identifier') as identifier then { srcId -> identifier then II(srcId, identifier) "baseIdentifier"; srcId where root = '1.2.250.1.71.4.2.1' -> identifier.type = create('CodeableConcept') as type then { srcId -> type.coding = create('Coding') as coding then { srcId -> coding.system = 'https://hl7.fr/ig/fhir/core/CodeSystem/fr-core-cs-v2-0203' "system"; srcId -> coding.code = 'IDNPS' "code"; } "coding"; } "idNatPs"; } "identifier"; src.addr as srcAddr -> tgt.address = create('Address') as address then ADAddress(srcAddr, address); src.telecom as srcTelecom -> tgt.telecom = create('ContactPoint') as contactPoint then TELContactPoint(srcTelecom, contactPoint); src.assignedPerson as person then { person.name as pName -> tgt.name = create('HumanName') as humanName then ENHumanName(pName, humanName); } "name"; } group ClinicalDocumentOrganization(source src : CustodianOrganization, target tgt : Organization) { src -> tgt.meta = create('Meta') as meta then { src -> meta.profile = 'https://interop.esante.gouv.fr/ig/fhir/annuaire/StructureDefinition/as-organization' "profile"; } "meta"; src.id as srcId -> tgt.identifier = create('Identifier') as identifier then II(srcId, identifier); src.name as v -> tgt.name = (v.other); src.telecom as srcTelecom -> tgt.telecom = create('ContactPoint') as contactPoint then TELContactPoint(srcTelecom, contactPoint); src.addr as srcAddr -> tgt.address = create('Address') as address then ADAddress(srcAddr, address); } group ClinicalDocumentPatientRole(source src : PatientRole, target tgt : Patient, target bundle : Bundle) { // src.id -> tgt.identifier; // Patient.identifier.type for EPR context, because there is only the identifier "LocalPid" allowed, LocalPid requires an type -> http://build.fhir.org/ig/hl7ch/ch-core/branches/master/StructureDefinition-ch-core-composition-patient-epr.html // Modification NR src.id as id -> tgt.identifier = create('Identifier') as identifier then setFrPatientIdentifier(id, identifier); src.addr as srcAddr -> tgt.address = create('Address') as address then ADAddress(srcAddr, address); src.telecom as srcTelecom -> tgt.telecom = create('ContactPoint') as contactPoint then TELContactPoint(srcTelecom, contactPoint); src.patient as patient then { patient.name as patientName -> tgt.name = create('HumanName') as humanName then ENHumanName(patientName, humanName); patient.administrativeGenderCode as gender then { gender.code as v -> tgt.gender = translate(v, 'https://interop.esante.gouv.fr/ig/fhir/mappingcdafhir/ConceptMap/cm-v3-administrative-gender', 'code') "gender"; } "gender"; patient.birthTime as birthTime -> tgt.birthDate = create('date') as date then TSDate(birthTime, date) "birthDate"; patient.deceasedInd as indicator where patient.deceasedTime.empty() -> tgt.deceased = create('boolean') as bool then boolean(indicator, bool) "deceasedBL"; patient.deceasedTime as dTime -> tgt.deceased = dTime; patient.maritalStatusCode as mStatus -> tgt.maritalStatus = mStatus "maritalStatus"; patient.languageCommunication as language -> tgt.communication as communication then { language.languageCode as lCode -> communication.language = lCode "communication"; // preference: for language.preferenceInd make communication.preferred } "language"; } "patientrole"; src.providerOrganization as org -> bundle.entry as e, e.resource = create('Organization') as organization, organization.id = uuid() as uuid3, e.fullUrl = append('urn:uuid:', uuid3) then { org -> tgt.managingOrganization = create('Reference') as reference, reference.reference = ('urn:uuid:' + %organization.id) "reference"; org.id as orgId -> organization.identifier = create('Identifier') as identifier then II(orgId, identifier); org.name as v -> organization.name = (v.other); org.telecom as orgTel -> organization.telecom = create('ContactPoint') as contactPoint then TELContactPoint(orgTel, contactPoint); org.addr as orgAddr -> organization.address = create('Address') as address then ADAddress(orgAddr, address); } "organization"; } group setFrPatientIdentifier(source id : II, target identifier : Identifier) { id -> identifier, identifier.type as type, type.coding as coding, coding.system as system, coding.code as code then { // src -> system.value = 'http://terminology.hl7.org/CodeSystem/v2-0203' "system"; // INS-NIR - 1.2.250.1.213.1.4.8 id where (id.root = '1.2.250.1.213.1.4.8') -> system.value = 'https://hl7.fr/ig/fhir/core/CodeSystem/fr-core-cs-v2-0203' "system"; id where (id.root = '1.2.250.1.213.1.4.8') -> code.value = 'INS-NIR' "code"; // IPP - 1.2.3.4.5.6.7.8.9.10 id where (id.root = '1.2.3.4.5.6.7.8.9.10') -> system.value = 'http://terminology.hl7.org/CodeSystem/v2-0203' "system"; id where (id.root = '1.2.3.4.5.6.7.8.9.10') -> code.value = 'PI' "code"; } "identifierType"; id.extension as extension -> identifier.value as value, value.value = extension; id.root as root -> identifier.system as system, system.value = append('urn:oid:', root); } // Fin modification NR // Etat initial // src -> tgt.identifier as identifier then { // src.id as id -> identifier.type as type, type.coding as coding, coding.system as system, coding.code as code then { // src -> system.value = 'http://terminology.hl7.org/CodeSystem/v2-0203' "system"; // src -> code.value = 'MR' "code"; // id.extension as extension -> identifier.value as value, value.value = extension "extension"; // id.root as root -> identifier.system as system, system.value = append('urn:oid:', root) "root"; // } "id"; // } "identifier"; // Fin état initial group ClinicalDocumentEncounter(source src : EncompassingEncounter, target bundle : Bundle, target tgt : Encounter) { src.id as Id -> tgt.identifier = create('Identifier') as identifier then II(Id, identifier); src -> tgt.status = 'finished' "status"; src.code as srcCode -> tgt.class = create('Coding') as coding then { srcCode.code as code -> coding.code = cast(code, 'string'); srcCode.codeSystem as system -> coding.system = translate(system, 'http://hl7.org/fhir/ConceptMap/special-oid2uri', 'uri'); srcCode.displayName as display -> coding.display = cast(display, 'string'); } "class"; src.code as srcCode -> tgt.type = create('CodeableConcept') as cc then CDCodeableConcept(srcCode, cc); src.effectiveTime as effTime -> tgt.period = create('Period') as period then IVLTSPeriod(effTime, period); src where admissionReferralSourceCode.exists() or dischargeDispositionCode.exists() -> tgt.hospitalization as hosp then { src.admissionReferralSourceCode as admRef -> hosp.admitSource = create('CodeableConcept') as cc then CDCodeableConcept(admRef, cc) "adminReferral"; src.dischargeDispositionCode as discDispo -> hosp.dischargeDisposition = create('CodeableConcept') as cc then CDCodeableConcept(discDispo, cc) "discDisposition"; } "hospitalization"; src.encounterParticipant as srcPart -> tgt.participant as tgtPart then { srcPart.typeCode as code -> tgtPart.type = cc('http://terminology.hl7.org/CodeSystem/v3-ParticipationType', code); srcPart.time as srcTime -> tgtPart.period = create('Period') as period then IVLTSPeriod(srcTime, period); srcPart.assignedEntity as entity -> bundle.entry as e, e.resource = create('Practitioner') as practitioner, practitioner.id = uuid() as uuid2, e.fullUrl = append('urn:uuid:', uuid2) then { entity -> tgtPart.individual = create('Reference') as reference, reference.reference = ('urn:uuid:' + %practitioner.id) then ClinicalDocumentEntityPractitioner(entity, practitioner) "entry"; } "entity"; } "participant"; src.location as srcLocation then { srcLocation.healthCareFacility as facility -> bundle.entry as e, e.resource = create('Location') as location, location.id = uuid() as uuid2, e.fullUrl = append('urn:uuid:', uuid2) then { facility -> tgt.location as tgtLocation, tgtLocation.location = create('Reference') as reference, reference.reference = ('urn:uuid:' + %location.id) then ClinicalDocumentLocation(facility, bundle, location) "facLocation"; } "facility"; }; } group ClinicalDocumentLocation(source src : HealthCareFacility, target bundle : Bundle, target tgt : Location) { src.id as srcIdentifier -> tgt.identifier = create('Identifier') as identifier then II(srcIdentifier, identifier); src.code as srcCode -> tgt.type = create('CodeableConcept') as cc then { srcCode.code as code -> cc.coding = create('Coding') as coding then { code -> coding.code = code "code"; code -> coding.system = 'https://mos.esante.gouv.fr/NOS/TRE_R02-SecteurActivite/FHIR/TRE-R02-SecteurActivite' "system"; srcCode.displayName as display -> coding.display = display "display"; } "coding"; } "locationType"; src.location as location then { // place names are usually stored with no parts location.name as srcName -> tgt.name = cast(srcName, 'string'); location.addr as locAddr -> tgt.address = create('Address') as address then ADAddress(locAddr, address); location.serviceProviderOrganization as srcOrg -> bundle.entry as e, e.resource = create('Organization') as organization, organization.id = uuid() as uuid3, e.fullUrl = append('urn:uuid:', uuid3) then { srcOrg -> tgt.managingOrganization = create('Reference') as reference, reference.reference = ('urn:uuid:' + %organization.id) then ClinicalDocumentOrganization(srcOrg, organization) "organization"; } "org"; }; // place names are usually stored with no parts } // _________________________ Template Type not specified ___________________ group NarrativeLink(source url, target ext : Extension) { url -> ext.url = 'http://hl7.org/fhir/StructureDefinition/narrativeLink' "url"; url -> ext.value = create('url') as value, value.value = url "value"; }