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
Le modèle présenté s’appuie sur un DPI théorique conçu pour persister les variables du socle nécessaires aux usages des Entrepôts de Données de Santé Hospitaliers (EDSH). L’objectif de cette modélisation est de disposer d’une représentation conceptuelle stable, indépendante des implémentations logicielles spécifiques, afin de faciliter l’interopérabilité, la standardisation et la valorisation secondaire des données de santé.
Dans cette approche, chaque table du modèle physique du DPI est alignée conceptuellement avec une ou plusieurs ressources FHIR partageant une proximité sémantique et fonctionnelle. Cet alignement permet d’établir un pont entre les structures relationnelles historiques des systèmes d’information hospitaliers et les modèles d’échange normalisés portés par HL7 FHIR.
Les transformations nécessaires au passage du modèle source vers les profils FHIR sont organisées sous forme de groups, chacun correspondant à un ensemble cohérent de règles de transformation et de mapping. Cette structuration favorise la lisibilité, la maintenabilité et la réutilisation des règles d’alignement.
Une fois les éléments pertinents de la source identifiés (périmètre final) et les profils FHIR élaborés, il est possible de formaliser les règles d’alignement des premiers vers les seconds via la rédaction d’une StructureMap.
Vous pouvez trouver l’alignement formel entre le modèle physique du DPI et les profils FHIR : Alignement DPI vers FHIR.
// ---------------------------------------------------------
// 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' "id-use";
pid -> identifier.type = cc('http://terminology.hl7.org/CodeSystem/v2-0203', 'PI', 'Patient Identifier') "id-type";
pid -> identifier.system = 'https://hospital.eu/ehr/patient-id' "id-system";
pid -> identifier.value = pid "id-value";
} "patient-identifier";
// 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' "ins-use"; // 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') "ins-type";
ins -> insIdentifier.system = 'urn:oid:1.2.250.1.213.1.4.8' "ins-system";
ins -> insIdentifier.value = ins "ins-value";
} "ins-identifier";
// Name
src where src.nom.exists() or src.prenom.exists() -> tgt.name as name then {
src.nom as lastName -> name.family = lastName "family-name";
src.prenom as firstName -> name.given = firstName "given-name";
src -> name.use = 'official' "name-use"; // hypothèse un peu forte.
} "patient-name";
// Demographics On aurait pu faire plus élégant avec un ConceptMap
src.dateNaissance as birthDate -> tgt.birthDate = birthDate "birth-date";
src.sexe as gender where gender = 'h' -> tgt.gender = 'male' "gender-male";
src.sexe as gender where gender = 'f' -> tgt.gender = 'female' "gender-female";
src.sexe as gender where gender.exists() and gender != 'h' and gender != 'f' -> tgt.gender = 'unknown' "gender-unknown"; // 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 "multiple-birth";
}
// ========================================================================
// 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') "id-type";
src -> identifier.system = 'https://hospital.eu/ehr/pmsi-id' "encounter-id-system";
src.pmsiId as srcEncoutnerId -> identifier.value = srcEncoutnerId "encounter-id-value";
} "encounter-identifier";
// Status - default to finished for historical data
src -> tgtEnc.status = 'finished' "encounter-status";
// Class - assume inpatient for PMSI data
src -> tgtEnc.class = c('http://terminology.hl7.org/CodeSystem/v3-ActCode', 'IMP', 'inpatient encounter') "encounter-class";
// Reference patient
src -> tgtEnc.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-reference" ;
} "encounter-subject";
// Period
src where src.dateDebutSejour.exists() or src.dateFinSejour.exists() -> tgtEnc.period as period then {
src.dateDebutSejour as startDate -> period.start = startDate "period-start";
src.dateFinSejour as endDate -> period.end = endDate "period-end";
} "encounter-period";
/* 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 "admit-source-text";
} "admit-source";
src.modeSortie as dischargeDisp -> hosp.dischargeDisposition as dischargeDsp then {
dischargeDisp -> dischargeDsp.text = dischargeDisp "discharge-disp-text";
} "discharge-disposition";
} "encounter-hospitalization";
// 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))) "provider-display";
} "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' "condition-id-system";
diagId -> identifier.value = diagId "condition-id-value";
} "condition-identifier";
// 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') "category-encounterDiag";
src.typeDiagnostic as diagType -> tgtCond.category as category then {
diagType -> category.text = diagType "category-text";
} "condition-category";
// 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 "condition-code" ;
src.libelleDiagnostic as text -> conditionCode.text = text "code-text";
} "var-code";
// Subject
src -> tgtCond.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "condition-subject";
// Encounter reference
src -> tgtCond.encounter = create('Reference') as ref then {
encounter.id as encounterId -> ref.reference = ('Encounter/' + %encounterId),
ref.type = 'Encounter' "encounter-ref";
} "condition-encounter";
// Recorded date
src.dateRecueil as entryDate -> tgtCond.recordedDate = entryDate "condition-recorded-date";
// Clinical status - assume active for recorded diagnoses
src -> tgtCond.clinicalStatus = cc('http://terminology.hl7.org/CodeSystem/condition-clinical', 'active') "condition-clinical-status";
// Verification status - assume confirmed for coded diagnoses
src -> tgtCond.verificationStatus = cc('http://terminology.hl7.org/CodeSystem/condition-ver-status', 'confirmed') "condition-verification-status";
}
// ========================================================================
// 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' "procedure-id-system";
acteId -> identifier.value = acteId "procedure-id-value";
} "procedure-identifier";
// Status - assume completed for historical data
src -> tgtProc.status = 'completed' "procedure-status";
// 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 "procedure-coding";
src.libelleActe as text -> procedureCode.text = text "code-text";
} "procedure-code";
// Subject
src -> tgtProc.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "procedure-subject";
// Encounter reference
src -> tgtProc.encounter = create('Reference') as ref then {
encounter.id as encounterId -> ref.reference = ('Encounter/' + %encounterId),
ref.type = 'Encounter' "encounter-ref";
} "procedure-encounter";
// Performed date/time
src.dateActe as performedDate where %performedDate.exists() -> tgtProc.performed = performedDate "procedure-performed";
// Performer
src.executant as performer -> tgtProc.performer as perf then {
performer -> perf.actor as actor then {
performer -> actor.display = performer "performer-display";
performer -> actor.type = 'Practitioner' "performerType";
} "performer-actor";
performer -> perf.function as function, function.text = 'Exécutant' "performerFunction";
} "procedure-performer";
}
// ========================================================================
// 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' "lab-id-system";
bioId -> identifier.value = bioId "lab-id-value";
} "lab-identifier";
// Status - map validation status or default to final
src.statutValidation as validation where validation = 'VALIDE' -> tgtObs.status = 'final' "status-validated";
src where src.statutValidation.exists().not() -> tgtObs.status = 'final' "status-default";
// Category
src -> tgtObs.category as category then {
src -> category.coding as coding then {
src -> coding.system = 'http://terminology.hl7.org/CodeSystem/observation-category' "category-system";
src -> coding.code = 'laboratory' "category-code";
src -> coding.display = 'Laboratory' "category-display";
} "category-coding";
} "lab-category";
// Code (LOINC)
src.codeLoinc as loinc -> tgtObs.code as code then {
loinc -> code.coding as coding then {
loinc -> coding.system = 'http://loinc.org' "code-system";
loinc -> coding.code = loinc "code-value";
} "lab-coding";
src.libelleTest as text -> code.text = text "code-text";
} "lab-code";
// Subject
src -> tgtObs.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "lab-subject";
// Effective date/time
src.datePrelevement as collectionDate where %collectionDate.exists() -> tgtObs.effective = cast(collectionDate, 'dateTime') "lab-effective";
// Value (numeric or text)
src.valeur as numValue where numValue.exists() -> tgtObs.value = create('Quantity') as qty then {
numValue -> qty.value = numValue "quantity-value";
src.unite as unit -> qty.unit = unit "quantity-unit";
src.unite as unit -> qty.code = unit "quantity-code";
src -> qty.system = 'http://unitsofmeasure.org' "quantity-system";
} "lab-value-quantity";
// 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 "ref-low-value";
src.unite as unit -> lowQty.unit = unit "ref-low-unit";
src.unite as unit -> lowQty.code = unit "ref-low-code";
src -> lowQty.system = 'http://unitsofmeasure.org' "ref-low-system";
} "reference-low";
src.borneSupNormale as high -> range.high as highQty then {
high -> highQty.value = high "ref-high-value";
src.unite as unit -> highQty.unit = unit "ref-high-unit";
src.unite as unit -> highQty.code = unit "ref-high-code";
src -> highQty.system = 'http://unitsofmeasure.org' "ref-high-system";
} "reference-high";
} "lab-reference-range";
// Performer
src.laboratoire as lab -> tgtObs.performer as performer then {
lab -> performer.display = lab "performer-display";
lab -> performer.type = 'Organization' "performerType";
} "lab-performer";
}
// ========================================================================
// 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' "med-request-id-system";
presId -> identifier.value = presId "med-request-id-value";
} "med-request-identifier";
// Status - assume unknown for historical prescriptions
src -> tgtMedReq.status = 'unknown' "med-request-status";
// Intent
src -> tgtMedReq.intent = 'order' "med-request-intent";
// Subject
src -> tgtMedReq.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "med-request-subject";
// Medication
src.denomination as denomination -> tgtMedReq.medication = create('CodeableConcept') as medication then {
denomination as text -> medication.text = text "medication-text";
src.codeAtc as atc -> medication.coding as coding then {
atc -> coding.system = 'http://www.whocc.no/atc' "atc-system";
atc -> coding.code = atc "atc-code";
} "atc-coding";
} "medication-denomination";
// Authored date
src.datePrescription as prescDate -> tgtMedReq.authoredOn = prescDate "med-authored";
// Requester
src.prescripteur as prescriber -> tgtMedReq.requester as requesterRef then {
prescriber -> requesterRef.display = prescriber "requester-display";
prescriber -> requesterRef.type = 'Practitioner' "requesterType";
} "med-requester";
}
// ========================================================================
// 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')
) "dosage-text";
// Route of administration from prescription
srcPres.voieAdministration as route -> dosageInstruction.route as routeCC then {
route -> routeCC.coding as routeCoding then {
route -> routeCoding.code = route "route-code";
route -> routeCoding.system = 'https://smt.esante.gouv.fr/terminologie-standardterms' "route-system";
} "route-coding";
} "dosage-route";
// 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 "period-start";
srcPres.dateFinPrescription as endDate -> medReqPeriod.end = endDate "period-end";
} "prescription-period";
// Frequency from posology
srcPoso.nombrePrisesParJour as frequency -> timingRepeat.frequency = frequency "timing-frequency";
srcPoso.nombrePrisesParJour as frequency -> timingRepeat.period = '1' "timing-period";
srcPoso.nombrePrisesParJour as frequency -> timingRepeat.periodUnit = 'd' "timing-period-unit";
} "timing-repeat";
} "dosage-timing";
// Dose and rate
srcPoso.quantite as quantity -> dosageInstruction.doseAndRate as doseRate then {
quantity -> doseRate.dose = create('Quantity') as doseQuantity then {
quantity -> doseQuantity.value = quantity "dose-value";
srcPoso.uniteQuantite as unit -> doseQuantity.unit = unit "dose-unit";
srcPoso.uniteQuantite as unit -> doseQuantity.code = unit "dose-code";
srcPoso -> doseQuantity.system = 'http://unitsofmeasure.org' "dose-system";
} "dose-quantity";
} "dose-and-rate";
// Maximum dose per period calculation (daily total) <- ça génère des choses non modélisées à date.
/* srcPoso where srcPoso.quantite.exists() and srcPoso.nombrePrisesParJour.exists() ->
dosageInstruction.maxDosePerPeriod as maxDose then {
srcPoso -> maxDose.numerator = create('Quantity') as numerator then {
srcPoso -> numerator.value = (%srcPoso.quantite * %srcPoso.nombrePrisesParJour) "max-dose-value";
srcPoso.uniteQuantite as unit -> numerator.unit = unit "max-dose-unit";
srcPoso.uniteQuantite as unit -> numerator.code = unit "max-dose-code";
srcPoso -> numerator.system = 'http://unitsofmeasure.org' "max-dose-system";
} "max-dose-numerator";
srcPoso -> maxDose.denominator = create('Quantity') as denominator then {
srcPoso -> denominator.value = 1 "max-dose-period-value";
srcPoso -> denominator.unit = 'd' "max-dose-period-unit";
srcPoso -> denominator.code = 'd' "max-dose-period-code";
srcPoso -> denominator.system = 'http://unitsofmeasure.org' "max-dose-period-system";
} "max-dose-denominator";
} "max-dose-per-period"; */
} "dosage-instruction";
}
// ========================================================================
// 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' "soin-id-system";
soinId -> identifier.value = soinId "soin-id-value";
} "med-request-identifier";
// 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' "category-system";
src -> coding.code = 'vital-signs' "category-code";
} "category-coding";
} "category";
// code (LOINC)
src -> tgtObs.code as code then {
src -> code.coding as coding then {
src -> coding.system = 'http://loinc.org' "code-system";
src.codeLoinc as codeLoinc -> coding.code = codeLoinc "code-value";
} "coding";
src.libelleTest as label -> code.text = label "code-text";
} "code";
// Subject
src -> tgtObs.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "obs-subject";
// 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' "lifestyle-id-system";
lsId -> identifier.value = (%lsId & '-tobacco') "lifestyle-id-value";
} "lifestyle-identifier";
// Status
src -> tgtObs.status = 'final' "lifestyle-status";
// 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' "category-system";
src -> coding.code = 'social-history' "category-code";
src -> coding.display = 'Social History' "category-display";
} "category-coding";
} "lifestyle-category";
// Code - tobacco use status
src -> tgtObs.code as code then {
src -> code.coding as coding then {
src -> coding.system = 'http://loinc.org' "code-system";
src -> coding.code = '72166-2' "code-value";
src -> coding.display = 'Tobacco smoking status' "code-display";
} "tobacco-coding";
} "tobacco-code";
// Subject
src -> tgtObs.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "lifestyle-subject";
// Effective date
src.dateRecueil as collectDate where %collectDate.exists() -> tgtObs.effective = cast(collectDate, 'dateTime') "lifestyle-effective";
// Value
src.consommationTabac as tobacco -> tgtObs.value = create('CodeableConcept') as valueCC then {
tobacco -> valueCC.text = tobacco "tobacco-value-text";
} "tobacco-value";
}
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' "lifestyle-id-system";
lsId -> identifier.value = (%lsId & '-alcohol') "lifestyle-id-value";
} "lifestyle-identifier";
// Status
src -> tgtObs.status = 'final' "lifestyle-status";
// 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' "category-system";
src -> coding.code = 'social-history' "category-code";
src -> coding.display = 'Social History' "category-display";
} "category-coding";
} "lifestyle-category";
// Code - alcohol use
src -> tgtObs.code as code then {
src -> code.coding as coding then {
src -> coding.system = 'http://loinc.org' "code-system";
src -> coding.code = '11331-6' "code-value";
src -> coding.display = 'History of alcohol use' "code-display";
} "alcohol-coding";
} "alcohol-code";
// Subject
src -> tgtObs.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "lifestyle-subject";
// Effective date
src.dateRecueil as collectDate where %collectDate.exists() -> tgtObs.effective = cast(collectDate, 'dateTime') "lifestyle-effective";
// Value
src.consommationAlcool as alcohol -> tgtObs.value = create('CodeableConcept') as valueCC then {
alcohol -> valueCC.text = alcohol "alcohol-value-text";
} "alcohol-value";
}
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' "lifestyle-id-system";
lsId -> identifier.value = (%lsId & '-drugs') "lifestyle-id-value";
} "lifestyle-identifier";
// Status
src -> tgtObs.status = 'final' "lifestyle-status";
// 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' "category-system";
src -> coding.code = 'social-history' "category-code";
src -> coding.display = 'Social History' "category-display";
} "category-coding";
} "lifestyle-category";
// Code - drug use
src -> tgtObs.code as code then {
src -> code.coding as coding then {
src -> coding.system = 'http://loinc.org' "code-system";
src -> coding.code = '11342-3' "code-value";
src -> coding.display = 'History of drug use' "code-display";
} "drug-coding";
} "drug-code";
// Subject
src -> tgtObs.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "lifestyle-subject";
// Effective date
src.dateRecueil as collectDate where %collectDate.exists() -> tgtObs.effective = cast(collectDate, 'dateTime') "lifestyle-effective";
// Value
src.consommationAutresDrogues as drugs -> tgtObs.value = create('CodeableConcept') as valueCC then {
drugs -> valueCC.text = drugs "drug-value-text";
} "drug-value";
}
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' "lifestyle-id-system";
lsId -> identifier.value = (%lsId & '-physical') "lifestyle-id-value";
} "lifestyle-identifier";
// Status
src -> tgtObs.status = 'final' "lifestyle-status";
// 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' "category-system";
src -> coding.code = 'social-history' "category-code";
src -> coding.display = 'Social History' "category-display";
} "category-coding";
} "lifestyle-category";
// Code - physical activity
src -> tgtObs.code as code then {
src -> code.coding as coding then {
src -> coding.system = 'http://loinc.org' "code-system";
src -> coding.code = '67504-6' "code-value";
src -> coding.display = 'Exercise activity' "code-display";
} "physical-coding";
} "physical-code";
// Subject
src -> tgtObs.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "lifestyle-subject";
// Effective date
src.dateRecueil as collectDate where %collectDate.exists() -> tgtObs.effective = cast(collectDate, 'dateTime') "lifestyle-effective";
// Value
src.activitePhysique as physical -> tgtObs.value = create('CodeableConcept') as valueCC then {
physical -> valueCC.text = physical "physical-value-text";
} "physical-value";
}
// ========================================================================
// 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' "admin-id-system";
adminId -> identifier.value = adminId "admin-id-value";
} "admin-identifier";
// Status - assume completed
src -> tgtMedAdmin.status = 'completed' "admin-status";
// Subject
src -> tgtMedAdmin.subject = create('Reference') as ref then {
patient.id as patientId -> ref.reference = ('Patient/' + %patientId),
ref.type = 'Patient' "subject-ref";
} "admin-subject";
// Medication
src.denomination as denomination -> tgtMedAdmin.medication = create('CodeableConcept') as medication then {
denomination -> medication.text = denomination "medication-text";
src.codeAtc as atc -> medication.coding as coding then {
atc -> coding.system = 'http://www.whocc.no/atc' "atc-system";
atc -> coding.code = atc "atc-code";
} "atc-coding";
} "admin-medication";
// 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 "period-start";
src.dateHeureFin as endDate -> period.end = endDate "period-end";
} "admin-effective";
// 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 "dose-value";
src.uniteQuantite as unit -> dose.unit = unit "dose-unit";
} "admin-dose";
src.voieAdministration as route -> dosage.route as routeCC then {
route -> routeCC.coding as routeCoding then {
route -> routeCoding.code = route "route-code";
route -> routeCoding.system = 'https://smt.esante.gouv.fr/terminologie-standardterms' "route-system";
} "route-coding";
} "dosage-route";
} "admin-dosage";
}
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' "encounter-ref";
} "admin-context";
}
group setEntryRequestAndFullUrl(source newRes, target tgtEntry) {
newRes.id as newResId then {
newResId -> tgtEntry.fullUrl = ('urn:ehr:' + %newRes.type().name + '/' + %newResId) "setFullUrl";
//newResId -> tgtEntry.request as tgtEntryRequest,
// tgtEntryRequest.method = 'POST',
// tgtEntryRequest.url = (%newRes.type().name + '/' + %newResId) "setRequest" ;
} "setResourceType";
}