Data Management with FHIR
0.1.0 - ci-build
Data Management with 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.aphp.fr/ig/fhir/dm/StructureMap/EHR2FSL | Version: 0.1.0 | |||
| Draft as of 2025-10-23 | Computable Name: EHR2FSL | |||
Transforms EHR logical model data to FHIR Semantic Layer resources using Bundle as container
map "https://interop.aphp.fr/ig/fhir/dm/StructureMap/EHR2FSL" = "EHR2FSL" // Transforms EHR logical model data to FHIR Semantic Layer resources using Bundle as container uses "https://interop.aphp.fr/ig/fhir/dm/StructureDefinition/ehr" alias EHR as source uses "http://hl7.org/fhir/StructureDefinition/Bundle" alias Bundle as target group EHR2FSL(source src : EHR, target bundle : Bundle) { // Initialize Bundle src -> bundle.id = uuid() "bundleid"; src -> bundle.type = 'collection' "bundletype"; src -> (now()) as timestamp then { src -> bundle.timestamp = timestamp "affectTimestamp"; } "setTimestamp"; // Transform Patient src.patient as patient -> bundle.entry as patientEntry then { patient -> patientEntry.resource = create('Patient') as dmPatient then { patient then TransformPatient(patient, dmPatient) "transformpatient"; patient then setEntryRequestAndFullUrl(dmPatient, patientEntry) "setRequestAndFullUrl"; src.patientAdresse as patientAdresse where patientAdresse.patientId = %patient.patientId then setAddress(patientAdresse, dmPatient) "transformpatientAdresse"; // Transform PMSI Encounters src.donneesPmsi as pmsi where pmsi.patientId = %patient.patientId -> bundle.entry as encounterEntry then { pmsi -> encounterEntry.resource = create('Encounter') as dmEncounter then { pmsi then TransformEncounter(pmsi, dmEncounter, dmPatient) "transformencounter"; pmsi then setEntryRequestAndFullUrl(dmEncounter, encounterEntry) "setRequestAndFullUrl"; // Transform Conditions (Diagnostics) src.diagnostics as diag where (diag.patientId = %patient.patientId) and (diag.pmsiId = %pmsi.pmsiId) -> bundle.entry as conditionEntry then { diag -> conditionEntry.resource = create('Condition') as dmCondition then { diag then TransformCondition(diag, dmCondition, dmPatient, dmEncounter) "transformcondition"; diag then setEntryRequestAndFullUrl(dmCondition, conditionEntry) "setRequestAndFullUrl"; } "createcondition"; } "conditionentries"; // Transform Procedures (Actes) src.actes as acte where (acte.patientId = %patient.patientId) and (acte.pmsiId = %pmsi.pmsiId) -> bundle.entry as procedureEntry then { acte -> procedureEntry.resource = create('Procedure') as dmProcedure then { acte then TransformProcedure(acte, dmProcedure, dmPatient, dmEncounter) "transformprocedure"; acte then setEntryRequestAndFullUrl(dmProcedure, procedureEntry) "setRequestAndFullUrl"; } "createprocedure"; } "procedureentries"; } "createencounter"; } "encounterentries"; // Transform Laboratory Observations src.biologie as lab where lab.patientId = %patient.patientId -> bundle.entry as labEntry then { lab -> labEntry.resource = create('Observation') as dmLab then { lab then TransformLabObservation(lab, dmLab, dmPatient) "transformLabObs"; lab then setEntryRequestAndFullUrl(dmLab, labEntry) "setRequestAndFullUrl"; } "createlabobs"; } "labentries"; // Transform Medication Requests src.prescription as pres where pres.patientId = %patient.patientId -> bundle.entry as medReqEntry then { pres -> medReqEntry.resource = create('MedicationRequest') as dmMedReq then { src then TransformMedicationRequest(pres, dmMedReq, dmPatient) "createmedrequest"; pres then setEntryRequestAndFullUrl(dmMedReq, medReqEntry) "setRequestAndFullUrl"; src.posologie as poso where %poso.prescriptionId = %pres.prescriptionId then setPoso(poso, pres, dmMedReq) "transformPoso"; // Transform Administration (W prescription) src.administration as admin where (admin.patientId = %patient.patientId) and (admin.prescriptionId = pres.prescriptionId) -> bundle.entry as adminEntry then { admin -> adminEntry.resource = create('MedicationAdministration') as dmAdmin then { admin then TransformMedicationAdministrationWOrder(admin, dmAdmin, dmPatient, dmMedReq) "createmedadmin"; admin then setEntryRequestAndFullUrl(dmAdmin, adminEntry) "setRequestAndFullUrl"; } "createmedadmin"; } "administrationentries"; } "createmedreq"; } "medicationentries"; // Transform Lifestyle Observations - separate observation for each lifestyle element src.styleVie as lifestyle where lifestyle.patientId = %patient.patientId then { // Tobacco consumption observation lifestyle.consommationTabac as tobacco where tobacco.exists() -> bundle.entry as tobaccoEntry then { tobacco -> tobaccoEntry.resource = create('Observation') as tobaccoObs then { lifestyle then TransformTobaccoObservation(lifestyle, tobaccoObs, dmPatient) "createtobaccoobs"; lifestyle then setEntryRequestAndFullUrl(tobaccoObs, tobaccoEntry) "setRequestAndFullUrl"; } "createtobaccoobs"; } "tobaccoentries"; // Alcohol consumption observation lifestyle.consommationAlcool as alcohol where alcohol.exists() -> bundle.entry as alcoholEntry then { alcohol -> alcoholEntry.resource = create('Observation') as alcoholObs then { lifestyle then TransformAlcoholObservation(lifestyle, alcoholObs, dmPatient) "createalcoholobs"; lifestyle then setEntryRequestAndFullUrl(alcoholObs, alcoholEntry) "setRequestAndFullUrl"; } "createalcoholobs"; } "alcoholentries"; // Drug consumption observation lifestyle.consommationAutresDrogues as drugs where drugs.exists() -> bundle.entry as drugEntry then { drugs -> drugEntry.resource = create('Observation') as drugObs then { lifestyle then TransformDrugObservation(lifestyle, drugObs, dmPatient) "createdrugobs"; lifestyle then setEntryRequestAndFullUrl(drugObs, drugEntry) "setRequestAndFullUrl"; } "createdrugobs"; } "drugentries"; // Physical activity observation lifestyle.activitePhysique as physical where physical.exists() -> bundle.entry as physicalEntry then { physical -> physicalEntry.resource = create('Observation') as physicalObs then { lifestyle then TransformPhysicalActivityObservation(lifestyle, physicalObs, dmPatient) "createphysicalobs"; lifestyle then setEntryRequestAndFullUrl(physicalObs, physicalEntry) "setRequestAndFullUrl"; } "createphysicalobs"; } "physicalentries"; } "lifestyleentries"; // Transform Care Observations (s) src.dossierSoins as soin where soin.patientId = %patient.patientId -> bundle.entry as vitalEntry then { soin -> vitalEntry.resource = create('Observation') as dmObs then { soin then TransformVitalSigns(soin, dmObs, dmPatient) "createmesureobs"; soin then setEntryRequestAndFullUrl(dmObs, vitalEntry) "setRequestAndFullUrl"; } "createmesureobs"; } "vitalentries"; // Transform Administration (WO prescription) src.administration as admin where (admin.patientId = %patient.patientId) and admin.prescriptionId.empty() -> bundle.entry as adminEntry then { admin -> adminEntry.resource = create('MedicationAdministration') as dmAdmin then { admin then TransformMedicationAdministrationWOOrder(admin, dmAdmin, dmPatient) "createmedadmin"; admin then setEntryRequestAndFullUrl(dmAdmin, adminEntry) "setRequestAndFullUrl"; } "createmedadmin"; } "administrationentries"; } "createpatient"; } "patiententry"; } // --------------------------------------------------------- // PATIENT TRANSFORMATION // --------------------------------------------------------- group TransformPatient(source src, target tgt : Patient) { src -> tgt.id = uuid() "setId"; // src.patientId as id -> tgt.id = id "patient-id"; // Identifiers src.patientId as pid -> tgt.identifier as identifier then { pid -> identifier.use = 'usual' "iduse"; pid -> identifier.type = cc('http://terminology.hl7.org/CodeSystem/v2-0203', 'PI', 'Patient Identifier') "idtype"; pid -> identifier.system = 'https://hospital.eu/ehr/patient-id' "idsystem"; pid -> identifier.value = pid "idvalue"; } "patientidentifier"; // NIR identifier <- C'est pas terrible côté expression de besoin : qu'est ce qu'ils veulent quand ils disent NIR ? ça peut être le NSS, mais du coup c'est pas un identifier (un NSS peut correspondre à plusieurs ayant droits), et ce serait plutôt une info qui irait côté claim. ça peut aussi être l'ins-nir, mais du coup qu'est ce qu'ils attendent dans l'ins ? Bref, à mon avis, faut discuter cette variable au niveau du GT. // src.nir as nir where nir.exists() -> tgt.identifier as insIdentifier then { nir -> insIdentifier.use = 'official' "ins-use"; nir -> insIdentifier.type = cc('http://hl7.fr/fhir/CodeSystem/fr-v2-0203', 'INS-NIR') "ins-type"; nir -> insIdentifier.system = 'urn:oid:1.2.250.1.213.1.4.8' "ins-system"; nir -> insIdentifier.value = nir "ins-value"; } "ins-identifier"; // Alternative INS from ins field src.ins as ins where ins.exists() -> tgt.identifier as insIdentifier then { ins -> insIdentifier.use = 'official' "insuse"; // On part du principe qu'on n'est pas sur un old. ins -> insIdentifier.type = cc('https://hl7.fr/ig/fhir/core/CodeSystem/fr-core-cs-v2-0203', 'INS-NIR', 'NIR définitif') "instype"; ins -> insIdentifier.system = 'urn:oid:1.2.250.1.213.1.4.8' "inssystem"; ins -> insIdentifier.value = ins "insvalue"; } "insidentifier"; // Name src where src.nom.exists() or src.prenom.exists() -> tgt.name as name then { src.nom as lastName -> name.family = lastName "familyname"; src.prenom as firstName -> name.given = firstName "givenname"; src -> name.use = 'official' "nameuse"; // hypothèse un peu forte. } "patientname"; // Demographics On aurait pu faire plus élégant avec un ConceptMap src.dateNaissance as birthDate -> tgt.birthDate = birthDate "birthdate"; src.sexe as gender where gender = 'h' -> tgt.gender = 'male' "gendermale"; src.sexe as gender where gender = 'f' -> tgt.gender = 'female' "genderfemale"; src.sexe as gender where gender.exists() and (gender != 'h') and (gender != 'f') -> tgt.gender = 'unknown' "genderunknown"; // n'a pas grand sens en l'état des contraintes SQL // Death information src.dateDeces as deathDate where deathDate.exists() -> tgt.deceased = cast(deathDate, 'dateTime') as deceasedDate then { src.sourceDeces as deathSource where deathSource.exists() -> deceasedDate.extension as DeathSourceExtension then { deathSource -> DeathSourceExtension.url = 'https://interop.aphp.fr/ig/fhir/dm/StructureDefinition/DeathSource' "deathSourceUrl"; deathSource -> DeathSourceExtension.value = cast(deathSource, 'code') "deathSourceValue"; } "deathSource"; } "deathDate"; // Multiple birth src.rangGemellaire as twin where twin.exists() -> tgt.multipleBirth = twin "multiplebirth"; } // ======================================================================== // PATIENT_LOCATION TRANSFORMATION // ======================================================================== group setAddress(source src, target tgtPat : Patient) { src where src.latitude.exists() or src.longitude.exists() or src.codeIris.exists() or src.libelleIris.exists() or src.codeGeographiqueResidence.exists() then { src -> tgtPat.address as newAddress then { src where src.latitude.exists() or src.longitude.exists() -> newAddress.extension as geolocationExtension, geolocationExtension.url = 'http://hl7.org/fhir/StructureDefinition/geolocation' then { src.latitude as srcLat -> geolocationExtension.extension as tgtLat, tgtLat.url = 'latitude', tgtLat.value = srcLat "setLat"; src.longitude as srcLong -> geolocationExtension.extension as tgtLong, tgtLong.url = 'longitude', tgtLong.value = srcLong "setLong"; } "setCoordinates"; src where src.codeIris.exists() or src.libelleIris.exists() -> newAddress.line = (%src.libelleIris) as irisLine, irisLine.extension = create('Extension') as irisExtension, irisExtension.url = 'http://hl7.org/fhir/StructureDefinition/iso21090-ADXP-censusTract', irisExtension.value = (%src.codeIris) "setIris"; // src where src.codeIris.exists() or src.libelleIris.exists() -> newAddress.extension as irisExtension, // irisExtension.url = 'http://hl7.org/fhir/StructureDefinition/iso21090-ADXP-censusTract', // irisExtension.value = (iif(%src.codeIris.exists() and %src.libelleIris.exists(), %src.codeIris & ' - ' & %src.libelleIris, %src.codeIris & %src.libelleIris)) "setIris"; src.codeGeographiqueResidence as srcCodeGeographiqueResidence where src.codeGeographiqueResidence.exists() -> newAddress.extension as residencePmsiExtension, residencePmsiExtension.url = 'https://interop.aphp.fr/ig/fhir/dm/StructureDefinition/PmsiCodeGeo', residencePmsiExtension.value = cast(srcCodeGeographiqueResidence, 'code') "SetResidencePmsi"; src.dateRecueil as dateRecueil -> newAddress.period as newAddressPeriod, newAddressPeriod.start = dateRecueil "setDateRecueil"; } "createAddress"; } "checkSource"; } // ======================================================================== // ENCOUNTER TRANSFORMATION // ======================================================================== group TransformEncounter(source src, target tgtEnc : Encounter, source patient) { src -> tgtEnc.id = uuid() "setId"; // src.pmsiId as id -> tgt.id = id "encounter-id"; // Identifier src -> tgtEnc.identifier as identifier then { src -> identifier.type = cc('https://hl7.fr/ig/fhir/core/CodeSystem/fr-core-cs-identifier-type', 'VN', 'Visit Number') "idtype"; src -> identifier.system = 'https://hospital.eu/ehr/pmsi-id' "encounteridsystem"; src.pmsiId as srcEncoutnerId -> identifier.value = srcEncoutnerId "encounteridvalue"; } "encounteridentifier"; // Status - default to finished for historical data src -> tgtEnc.status = 'finished' "encounterstatus"; // Class - assume inpatient for PMSI data src -> tgtEnc.class = c('http://terminology.hl7.org/CodeSystem/v3-ActCode', 'IMP', 'inpatient encounter') "encounterclass"; // Reference patient src -> tgtEnc.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectreference"; } "encountersubject"; // Period src where src.dateDebutSejour.exists() or src.dateFinSejour.exists() -> tgtEnc.period as period then { src.dateDebutSejour as startDate -> period.start = startDate "periodstart"; src.dateFinSejour as endDate -> period.end = endDate "periodend"; } "encounterperiod"; // Désactivé dans le modèle physique // Duration src.dureeSejour as duration -> tgtEnc.length as length then { duration -> length.value = duration "length-value"; duration -> length.unit = 'd' "length-unit"; duration -> length.system = 'http://unitsofmeasure.org' "length-system"; duration -> length.code = 'd' "length-code"; } "encounter-length"; // Hospitalization src where src.modeEntree.exists() or src.modeSortie.exists() -> tgtEnc.hospitalization as hosp then { src.modeEntree as admitSource -> hosp.admitSource as admitSrc then { admitSource -> admitSrc.text = admitSource "admitsourcetext"; } "admitsource"; src.modeSortie as dischargeDisp -> hosp.dischargeDisposition as dischargeDsp then { dischargeDisp -> dischargeDsp.text = dischargeDisp "dischargedisptext"; } "dischargedisposition"; } "encounterhospitalization"; // Service Provider src where src.uniteFonctionnelle.exists() or src.service.exists() or src.etablissement.exists() -> tgtEnc.serviceProvider as provider then { src -> provider.display = (iif(%src.uniteFonctionnelle.exists(), %src.uniteFonctionnelle, iif(%src.service.exists(), %src.service, %src.etablissement))) "providerdisplay"; } "provider"; // // Location - On n'a pas de location à gérer à ce stade (établissement, service et UF, halluciné par l'IA, sont des Organization que j'ai du coup mi dans Provider. ) src.service as service -> tgt.location as location then { service -> location.location as loc then { service -> loc.display = service "location-display"; } "location-ref"; } "encounter-location"; } // ======================================================================== // CONDITION TRANSFORMATION // ======================================================================== group TransformCondition(source src, target tgtCond : Condition, source patient, source encounter) { src -> tgtCond.id = uuid() "setId"; // Identifier src.diagnosticId as diagId -> tgtCond.identifier as identifier then { diagId -> identifier.system = 'https://hospital.eu/ehr/diagnostic-id' "conditionidsystem"; diagId -> identifier.value = diagId "conditionidvalue"; } "conditionidentifier"; // Category // Représentation des codes diagnostics du PMSI dans condition , plusieurs options : // - ce sont des infos de claim, pas de condition // - utiliser la catégorie 'encounter-diagnosis' // - utiliser les catégories du PMSI : DP, DR, DAS, DAD // - mettre les deux précédent <- j'ai fait ça. src -> tgtCond.category = cc('http://terminology.hl7.org/CodeSystem/condition-category', 'encounter-diagnosis', 'Encounter Diagnosis') "categoryencounterDiag"; src.typeDiagnostic as diagType -> tgtCond.category as category then { diagType -> category.text = diagType "categorytext"; } "conditioncategory"; // Code (ICD-10) src.codeDiagnostic as srcCode -> tgtCond.code as conditionCode then { // la fonction cc ne peuple pas le display... src.libelleDiagnostic as label -> conditionCode.coding as tgtCoding, tgtCoding.system = 'http://hl7.org/fhir/sid/icd-10', tgtCoding.code = srcCode "conditioncode"; src.libelleDiagnostic as text -> conditionCode.text = text "codetext"; } "varcode"; // Subject src -> tgtCond.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "conditionsubject"; // Encounter reference src -> tgtCond.encounter = create('Reference') as ref then { encounter.id as encounterId -> ref.reference = ('Encounter/' + %encounterId), ref.type = 'Encounter' "encounterref"; } "conditionencounter"; // Recorded date src.dateRecueil as entryDate -> tgtCond.recordedDate = entryDate "conditionrecordeddate"; // Clinical status - assume active for recorded diagnoses src -> tgtCond.clinicalStatus = cc('http://terminology.hl7.org/CodeSystem/condition-clinical', 'active') "conditionclinicalstatus"; // Verification status - assume confirmed for coded diagnoses src -> tgtCond.verificationStatus = cc('http://terminology.hl7.org/CodeSystem/condition-ver-status', 'confirmed') "conditionverificationstatus"; } // ======================================================================== // PROCEDURE TRANSFORMATION // ======================================================================== group TransformProcedure(source src, target tgtProc : Procedure, source patient, source encounter) { src -> tgtProc.id = uuid() "setId"; // Identifier src.acteId as acteId -> tgtProc.identifier as identifier then { acteId -> identifier.system = 'https://hospital.eu/ehr/acte-id' "procedureidsystem"; acteId -> identifier.value = acteId "procedureidvalue"; } "procedureidentifier"; // Status - assume completed for historical data src -> tgtProc.status = 'completed' "procedurestatus"; // Code (CCAM) src.codeActe as code -> tgtProc.code as procedureCode then { code -> procedureCode.coding as coding, coding.system = 'https://interop.aphp.fr/ig/fhir/dm/CodeSystem/Ccam', coding.code = code "procedurecoding"; src.libelleActe as text -> procedureCode.text = text "codetext"; } "procedurecode"; // Subject src -> tgtProc.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "proceduresubject"; // Encounter reference src -> tgtProc.encounter = create('Reference') as ref then { encounter.id as encounterId -> ref.reference = ('Encounter/' + %encounterId), ref.type = 'Encounter' "encounterref"; } "procedureencounter"; // Performed date/time src.dateActe as performedDate where %performedDate.exists() -> tgtProc.performed = performedDate "procedureperformed"; // Performer src.executant as performer -> tgtProc.performer as perf then { performer -> perf.actor as actor then { performer -> actor.display = performer "performerdisplay"; performer -> actor.type = 'Practitioner' "performerType"; } "performeractor"; performer -> perf.function as function, function.text = 'Exécutant' "performerFunction"; } "procedureperformer"; } // ======================================================================== // LABORATORY OBSERVATION TRANSFORMATION // ======================================================================== group TransformLabObservation(source src, target tgtObs : Observation, source patient) { src -> tgtObs.id = uuid() "setId"; // Identifier src.biologieId as bioId -> tgtObs.identifier as identifier then { bioId -> identifier.system = 'https://hospital.eu/ehr/biologie-id' "labidsystem"; bioId -> identifier.value = bioId "labidvalue"; } "labidentifier"; // Status - map validation status or default to final src.statutValidation as validation where validation = 'VALIDE' -> tgtObs.status = 'final' "statusvalidated"; src where src.statutValidation.exists().not() -> tgtObs.status = 'final' "statusdefault"; // Category src -> tgtObs.category as category then { src -> category.coding as coding then { src -> coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "categorysystem"; src -> coding.code = 'laboratory' "categorycode"; src -> coding.display = 'Laboratory' "categorydisplay"; } "categorycoding"; } "labcategory"; // Code (LOINC) src.codeLoinc as loinc -> tgtObs.code as code then { loinc -> code.coding as coding then { loinc -> coding.system = 'http://loinc.org' "codesystem"; loinc -> coding.code = loinc "codevalue"; } "labcoding"; src.libelleTest as text -> code.text = text "codetext"; } "labcode"; // Subject src -> tgtObs.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "labsubject"; // Effective date/time src.datePrelevement as collectionDate where %collectionDate.exists() -> tgtObs.effective = cast(collectionDate, 'dateTime') "labeffective"; // Value (numeric or text) src.valeur as numValue where numValue.exists() -> tgtObs.value = create('Quantity') as qty then { numValue -> qty.value = numValue "quantityvalue"; src.unite as unit -> qty.unit = unit "quantityunit"; src.unite as unit -> qty.code = unit "quantitycode"; src -> qty.system = 'http://unitsofmeasure.org' "quantitysystem"; } "labvaluequantity"; // On n'a pas de résultats textuels de prévu // src.valeurTexte as textValue where textValue.exists() and src.valeur.exists().not() -> tgtObs.value = textValue "lab-value-string"; // Reference range src where src.borneInfNormale.exists() or src.borneSupNormale.exists() -> tgtObs.referenceRange as range then { src.borneInfNormale as low -> range.low as lowQty then { low -> lowQty.value = low "reflowvalue"; src.unite as unit -> lowQty.unit = unit "reflowunit"; src.unite as unit -> lowQty.code = unit "reflowcode"; src -> lowQty.system = 'http://unitsofmeasure.org' "reflowsystem"; } "referencelow"; src.borneSupNormale as high -> range.high as highQty then { high -> highQty.value = high "refhighvalue"; src.unite as unit -> highQty.unit = unit "refhighunit"; src.unite as unit -> highQty.code = unit "refhighcode"; src -> highQty.system = 'http://unitsofmeasure.org' "refhighsystem"; } "referencehigh"; } "labreferencerange"; // Performer src.laboratoire as lab -> tgtObs.performer as performer then { lab -> performer.display = lab "performerdisplay"; lab -> performer.type = 'Organization' "performerType"; } "labperformer"; } // ======================================================================== // MEDICATION REQUEST TRANSFORMATION // ======================================================================== group TransformMedicationRequest(source src, target tgtMedReq : MedicationRequest, source patient) { src -> tgtMedReq.id = uuid() "setId"; // Identifier src.prescriptionId as presId -> tgtMedReq.identifier as identifier then { presId -> identifier.system = 'https://hospital.eu/ehr/prescription-id' "medrequestidsystem"; presId -> identifier.value = presId "medrequestidvalue"; } "medrequestidentifier"; // Status - assume unknown for historical prescriptions src -> tgtMedReq.status = 'unknown' "medrequeststatus"; // Intent src -> tgtMedReq.intent = 'order' "medrequestintent"; // Subject src -> tgtMedReq.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "medrequestsubject"; // Medication src.denomination as denomination -> tgtMedReq.medication = create('CodeableConcept') as medication then { denomination as text -> medication.text = text "medicationtext"; src.codeAtc as atc -> medication.coding as coding then { atc -> coding.system = 'http://www.whocc.no/atc' "atcsystem"; atc -> coding.code = atc "atccode"; } "atccoding"; } "medicationdenomination"; // Authored date src.datePrescription as prescDate -> tgtMedReq.authoredOn = prescDate "medauthored"; // Requester src.prescripteur as prescriber -> tgtMedReq.requester as requesterRef then { prescriber -> requesterRef.display = prescriber "requesterdisplay"; prescriber -> requesterRef.type = 'Practitioner' "requesterType"; } "medrequester"; } // ======================================================================== // POSOLOGIE TRANSFORMATION // ======================================================================== group setPoso(source srcPoso, source srcPres, target tgtMedReq) { srcPres -> tgtMedReq.dosageInstruction as dosageInstruction then { // Human-readable dosage text - construct from available fields srcPoso -> dosageInstruction.text = ('Prendre' + iif(%srcPoso.quantite.exists(), ' ' + %srcPoso.quantite.toString(), '1') + iif(%srcPoso.uniteQuantite.exists(), ' ' + %srcPoso.uniteQuantite, ' comprimé') + iif(%srcPoso.nombrePrisesParJour.exists(), ' ' + %srcPoso.nombrePrisesParJour.toString() + ' fois par jour', ' selon prescription')) "dosagetext"; // Route of administration from prescription srcPres.voieAdministration as route -> dosageInstruction.route as routeCC then { route -> routeCC.coding as routeCoding then { route -> routeCoding.code = route "routecode"; route -> routeCoding.system = 'https://smt.esante.gouv.fr/terminologie-standardterms' "routesystem"; } "routecoding"; } "dosageroute"; // Timing srcPoso -> dosageInstruction.timing as timing then { srcPoso -> timing.repeat as timingRepeat then { // Set prescription period as bounds if available srcPres where srcPres.dateDebutPrescription.exists() or srcPres.dateFinPrescription.exists() -> timingRepeat.bounds = create('Period') as medReqPeriod then { srcPres.dateDebutPrescription as startDate -> medReqPeriod.start = startDate "periodstart"; srcPres.dateFinPrescription as endDate -> medReqPeriod.end = endDate "periodend"; } "prescriptionperiod"; // Frequency from posology srcPoso.nombrePrisesParJour as frequency -> timingRepeat.frequency = frequency "timingfrequency"; srcPoso.nombrePrisesParJour as frequency -> timingRepeat.period = '1' "timingperiod"; srcPoso.nombrePrisesParJour as frequency -> timingRepeat.periodUnit = 'd' "timingperiodunit"; } "timingrepeat"; } "dosagetiming"; // Dose and rate srcPoso.quantite as quantity -> dosageInstruction.doseAndRate as doseRate then { quantity -> doseRate.dose = create('Quantity') as doseQuantity then { quantity -> doseQuantity.value = quantity "dosevalue"; srcPoso.uniteQuantite as unit -> doseQuantity.unit = unit "doseunit"; srcPoso.uniteQuantite as unit -> doseQuantity.code = unit "dosecode"; srcPoso -> doseQuantity.system = 'http://unitsofmeasure.org' "dosesystem"; } "dosequantity"; } "doseandrate"; } "dosageinstruction"; } // ======================================================================== // VITAL SIGNS TRANSFORMATION // ======================================================================== group TransformVitalSigns(source src, target tgtObs : Observation, source patient) { src -> tgtObs.id = uuid() "setId"; // Identifier src.soinId as soinId -> tgtObs.identifier as identifier then { soinId -> identifier.system = 'https://hospital.eu/ehr/soin-id' "soinidsystem"; soinId -> identifier.value = soinId "soinidvalue"; } "medrequestidentifier"; // Status - assume final src -> tgtObs.status = 'final' "status"; // Category - vital-signs src -> tgtObs.category as category then { src -> category.coding as coding then { src -> coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "categorysystem"; src -> coding.code = 'vital-signs' "categorycode"; } "categorycoding"; } "category"; // code (LOINC) src -> tgtObs.code as code then { src -> code.coding as coding then { src -> coding.system = 'http://loinc.org' "codesystem"; src.codeLoinc as codeLoinc -> coding.code = codeLoinc "codevalue"; } "coding"; src.libelleTest as label -> code.text = label "codetext"; } "code"; // Subject src -> tgtObs.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "obssubject"; // Date de l'observation src.dateMesure as date where %date.exists() -> tgtObs.effective = cast(date, 'dateTime') "effective"; // Valeur (quantitiative) src -> tgtObs.value = create('Quantity') as qty then { src.valeur as mesure -> qty.value = mesure "value"; src.unite as unit -> qty.unit = unit "unit"; src.unite as unit -> qty.code = unit "code"; src -> qty.system = 'http://unitsofmeasure.org' "system"; } "quantity"; // Blood pressure observation - would need separate entry with components } // ======================================================================== // LIFESTYLE TRANSFORMATION - SEPARATE OBSERVATIONS // ======================================================================== group TransformTobaccoObservation(source src, target tgtObs : Observation, source patient) { src -> tgtObs.id = uuid() "setId"; // Identifier src.styleVieId as lsId -> tgtObs.identifier as identifier then { lsId -> identifier.system = 'https://hospital.eu/ehr/lifestyle-id' "lifestyleidsystem"; lsId -> identifier.value = (%lsId & '-tobacco') "lifestyleidvalue"; } "lifestyleidentifier"; // Status src -> tgtObs.status = 'final' "lifestylestatus"; // Category - social-history src -> tgtObs.category as category then { src -> category.coding as coding then { src -> coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "categorysystem"; src -> coding.code = 'social-history' "categorycode"; src -> coding.display = 'Social History' "categorydisplay"; } "categorycoding"; } "lifestylecategory"; // Code - tobacco use status src -> tgtObs.code as code then { src -> code.coding as coding then { src -> coding.system = 'http://loinc.org' "codesystem"; src -> coding.code = '72166-2' "codevalue"; src -> coding.display = 'Tobacco smoking status' "codedisplay"; } "tobaccocoding"; } "tobaccocode"; // Subject src -> tgtObs.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "lifestylesubject"; // Effective date src.dateRecueil as collectDate where %collectDate.exists() -> tgtObs.effective = cast(collectDate, 'dateTime') "lifestyleeffective"; // Value src.consommationTabac as tobacco -> tgtObs.value = create('CodeableConcept') as valueCC then { tobacco -> valueCC.text = tobacco "tobaccovaluetext"; } "tobaccovalue"; } group TransformAlcoholObservation(source src, target tgtObs : Observation, source patient) { src -> tgtObs.id = uuid() "setId"; // Identifier src.styleVieId as lsId -> tgtObs.identifier as identifier then { lsId -> identifier.system = 'https://hospital.eu/ehr/lifestyle-id' "lifestyleidsystem"; lsId -> identifier.value = (%lsId & '-alcohol') "lifestyleidvalue"; } "lifestyleidentifier"; // Status src -> tgtObs.status = 'final' "lifestylestatus"; // Category - social-history src -> tgtObs.category as category then { src -> category.coding as coding then { src -> coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "categorysystem"; src -> coding.code = 'social-history' "categorycode"; src -> coding.display = 'Social History' "categorydisplay"; } "categorycoding"; } "lifestylecategory"; // Code - alcohol use src -> tgtObs.code as code then { src -> code.coding as coding then { src -> coding.system = 'http://loinc.org' "codesystem"; src -> coding.code = '11331-6' "codevalue"; src -> coding.display = 'History of alcohol use' "codedisplay"; } "alcoholcoding"; } "alcoholcode"; // Subject src -> tgtObs.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "lifestylesubject"; // Effective date src.dateRecueil as collectDate where %collectDate.exists() -> tgtObs.effective = cast(collectDate, 'dateTime') "lifestyleeffective"; // Value src.consommationAlcool as alcohol -> tgtObs.value = create('CodeableConcept') as valueCC then { alcohol -> valueCC.text = alcohol "alcoholvaluetext"; } "alcoholvalue"; } group TransformDrugObservation(source src, target tgtObs : Observation, source patient) { src -> tgtObs.id = uuid() "setId"; // Identifier src.styleVieId as lsId -> tgtObs.identifier as identifier then { lsId -> identifier.system = 'https://hospital.eu/ehr/lifestyle-id' "lifestyleidsystem"; lsId -> identifier.value = (%lsId & '-drugs') "lifestyleidvalue"; } "lifestyleidentifier"; // Status src -> tgtObs.status = 'final' "lifestylestatus"; // Category - social-history src -> tgtObs.category as category then { src -> category.coding as coding then { src -> coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "categorysystem"; src -> coding.code = 'social-history' "categorycode"; src -> coding.display = 'Social History' "categorydisplay"; } "categorycoding"; } "lifestylecategory"; // Code - drug use src -> tgtObs.code as code then { src -> code.coding as coding then { src -> coding.system = 'http://loinc.org' "codesystem"; src -> coding.code = '11342-3' "codevalue"; src -> coding.display = 'History of drug use' "codedisplay"; } "drugcoding"; } "drugcode"; // Subject src -> tgtObs.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "lifestylesubject"; // Effective date src.dateRecueil as collectDate where %collectDate.exists() -> tgtObs.effective = cast(collectDate, 'dateTime') "lifestyleeffective"; // Value src.consommationAutresDrogues as drugs -> tgtObs.value = create('CodeableConcept') as valueCC then { drugs -> valueCC.text = drugs "drugvaluetext"; } "drugvalue"; } group TransformPhysicalActivityObservation(source src, target tgtObs : Observation, source patient) { src -> tgtObs.id = uuid() "setId"; // Identifier src.styleVieId as lsId -> tgtObs.identifier as identifier then { lsId -> identifier.system = 'https://hospital.eu/ehr/lifestyle-id' "lifestyleidsystem"; lsId -> identifier.value = (%lsId & '-physical') "lifestyleidvalue"; } "lifestyleidentifier"; // Status src -> tgtObs.status = 'final' "lifestylestatus"; // Category - social-history src -> tgtObs.category as category then { src -> category.coding as coding then { src -> coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "categorysystem"; src -> coding.code = 'social-history' "categorycode"; src -> coding.display = 'Social History' "categorydisplay"; } "categorycoding"; } "lifestylecategory"; // Code - physical activity src -> tgtObs.code as code then { src -> code.coding as coding then { src -> coding.system = 'http://loinc.org' "codesystem"; src -> coding.code = '67504-6' "codevalue"; src -> coding.display = 'Exercise activity' "codedisplay"; } "physicalcoding"; } "physicalcode"; // Subject src -> tgtObs.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "lifestylesubject"; // Effective date src.dateRecueil as collectDate where %collectDate.exists() -> tgtObs.effective = cast(collectDate, 'dateTime') "lifestyleeffective"; // Value src.activitePhysique as physical -> tgtObs.value = create('CodeableConcept') as valueCC then { physical -> valueCC.text = physical "physicalvaluetext"; } "physicalvalue"; } // ======================================================================== // MEDICATION ADMINISTRATION TRANSFORMATION // ======================================================================== group TransformMedicationAdministrationWOOrder(source src, target tgtMedAdmin : MedicationAdministration, source patient) { src -> tgtMedAdmin.id = uuid() "setId"; // Identifier src.administrationId as adminId -> tgtMedAdmin.identifier as identifier then { adminId -> identifier.system = 'https://hospital.eu/ehr/administration-id' "adminidsystem"; adminId -> identifier.value = adminId "adminidvalue"; } "adminidentifier"; // Status - assume completed src -> tgtMedAdmin.status = 'completed' "adminstatus"; // Subject src -> tgtMedAdmin.subject = create('Reference') as ref then { patient.id as patientId -> ref.reference = ('Patient/' + %patientId), ref.type = 'Patient' "subjectref"; } "adminsubject"; // Medication src.denomination as denomination -> tgtMedAdmin.medication = create('CodeableConcept') as medication then { denomination -> medication.text = denomination "medicationtext"; src.codeAtc as atc -> medication.coding as coding then { atc -> coding.system = 'http://www.whocc.no/atc' "atcsystem"; atc -> coding.code = atc "atccode"; } "atccoding"; } "adminmedication"; // Effective period src where src.dateHeureDebut.exists() or src.dateHeureFin.exists() -> tgtMedAdmin.effective = create('Period') as period then { src.dateHeureDebut as startDate -> period.start = startDate "periodstart"; src.dateHeureFin as endDate -> period.end = endDate "periodend"; } "admineffective"; // Dosage src where src.quantite.exists() or src.voieAdministration.exists() -> tgtMedAdmin.dosage as dosage then { src.quantite as quantity -> dosage.dose = create('Quantity') as dose then { quantity -> dose.value = quantity "dosevalue"; src.uniteQuantite as unit -> dose.unit = unit "doseunit"; } "admindose"; src.voieAdministration as route -> dosage.route as routeCC then { route -> routeCC.coding as routeCoding then { route -> routeCoding.code = route "routecode"; route -> routeCoding.system = 'https://smt.esante.gouv.fr/terminologie-standardterms' "routesystem"; } "routecoding"; } "dosageroute"; } "admindosage"; } group TransformMedicationAdministrationWOrder(source src, target tgtMedAdmin : MedicationAdministration, source patient, source order) extends TransformMedicationAdministrationWOOrder { // Context (encounter) reference src -> tgtMedAdmin.request = create('Reference') as ref then { order.id as orderId -> ref.reference = ('MedicationRequest/' + %orderId), ref.type = 'MedicationRequest' "encounterref"; } "admincontext"; } group setEntryRequestAndFullUrl(source newRes, target tgtEntry) { newRes.id as newResId then { newResId -> tgtEntry.fullUrl = ('urn:ehr:' + %newRes.type().name + '/' + %newResId) "setFullUrl"; } "setResourceType"; }