Data Management with FHIR 0.1.0 - ci-build

Data Management with FHIR - version de développement local (intégration continue v0.1.0) construite par les outils de publication FHIR (HL7® FHIR® Standard). Voir le répertoire des versions publiées

## StructureMap: Transforms EHR logical model data to FHIR Semantic Layer resources using Bundle as container

Official URL: https://interop.aphp.fr/ig/fhir/dm/StructureMap/EHR2FSL | Version: 0.1.0
Draft as of 2026-04-16 | 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";
    }