From 4beb6db22e9d7a21f710b76038e2d613fec8e857 Mon Sep 17 00:00:00 2001 From: Mary McGrath Date: Fri, 15 Nov 2024 14:20:50 -0500 Subject: [PATCH 1/3] fix: get all emergency contacts (#2909) * fix: point to FHIR-Converter branch * test: add coverage for changes in snapshots * fix: handle viewer side of showing multiple contacts * fix: formatting of multiple emergency contact numbers * fix: weird parens * Update containers/fhir-converter/Dockerfile --- .../ecr-viewer/src/app/api/fhirPath.yml | 2 +- .../app/services/evaluateFhirDataService.ts | 36 +- .../services/evaluateFhirDataServices.test.ts | 70 + containers/fhir-converter/Dockerfile | 2 +- .../__snapshots__/test_FHIR-Converter.ambr | 49 + .../CDA_eICR.xml | 3273 ++++++++++++++++- 6 files changed, 3413 insertions(+), 19 deletions(-) diff --git a/containers/ecr-viewer/src/app/api/fhirPath.yml b/containers/ecr-viewer/src/app/api/fhirPath.yml index 238326dc74..d5593774bf 100644 --- a/containers/ecr-viewer/src/app/api/fhirPath.yml +++ b/containers/ecr-viewer/src/app/api/fhirPath.yml @@ -20,7 +20,7 @@ patientEthnicity: "Bundle.entry.resource.where(resourceType = 'Patient').extensi patientEthnicityDetailed: "Bundle.entry.resource.where(resourceType = 'Patient').extension.where(url = 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity').extension.where(url = 'detailed').valueCoding.display" patientLanguage: "Bundle.entry.resource.where(resourceType = 'Patient').communication.first().language.coding.first().display" patientTribalAffiliation: "Bundle.entry.resource.where(resourceType = 'Patient').extension.where(url='http: //hl7.org/fhir/us/ecr/StructureDefinition/us-ph-tribal-affiliation-extension').extension.where(url='TribeName').value.display" -patientEmergencyContact: "Bundle.entry.resource.where(resourceType = 'Patient').contact.first()" +patientEmergencyContact: "Bundle.entry.resource.where(resourceType = 'Patient').contact" # Social History patientCurrentJobTitle: "Bundle.entry.resource.where(resourceType='Observation').where(meta.profile='http://hl7.org/fhir/us/odh/StructureDefinition/odh-PastOrPresentJob').where(effectivePeriod.end.exists().not()).iif(valueCodeableConcept.coding.display.exists(), valueCodeableConcept.coding.display, valueString)" diff --git a/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts b/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts index 79a8ae6b32..f6241d96bb 100644 --- a/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts +++ b/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts @@ -507,26 +507,30 @@ export const evaluateEmergencyContact = ( fhirBundle: Bundle, mappings: PathMappings, ) => { - const contact = evaluate( - fhirBundle, - mappings.patientEmergencyContact, - )[0] as PatientContact; + const contacts = (evaluate(fhirBundle, mappings.patientEmergencyContact) ?? + []) as PatientContact[]; - if (contact) { - const relationship = contact.relationship?.[0].coding?.[0]?.display; + if (contacts.length === 0) return undefined; - const address = formatAddress( - contact.address?.line, - contact.address?.city, - contact.address?.state, - contact.address?.postalCode, - contact.address?.country, - ); + return contacts + .map((contact) => { + const relationship = contact.relationship?.[0].coding?.[0]?.display; - const phoneNumbers = formatContactPoint(contact.telecom); + const address = formatAddress( + contact.address?.line, + contact.address?.city, + contact.address?.state, + contact.address?.postalCode, + contact.address?.country, + ); - return [relationship, address, phoneNumbers].filter(Boolean).join("\n"); - } + const phoneNumbers = formatContactPoint(contact.telecom); + + return [relationship, address, ...phoneNumbers] + .filter(Boolean) + .join("\n"); + }) + .join("\n\n"); }; /** diff --git a/containers/ecr-viewer/src/app/tests/services/evaluateFhirDataServices.test.ts b/containers/ecr-viewer/src/app/tests/services/evaluateFhirDataServices.test.ts index 98e6cd0cdc..ca7f593ff4 100644 --- a/containers/ecr-viewer/src/app/tests/services/evaluateFhirDataServices.test.ts +++ b/containers/ecr-viewer/src/app/tests/services/evaluateFhirDataServices.test.ts @@ -230,6 +230,76 @@ describe("Evaluate Emergency Contact", () => { `sister\n999 Single Court\nBEVERLY HILLS, CA\n90210, USA\nHome: 615-995-9999`, ); }); + it("should return multiple emergency contacts", () => { + const BundleWithPatientAndContact = JSON.parse( + JSON.stringify(BundleWithPatient), + ) as unknown as Bundle; + const patientIndex = BundleWithPatientAndContact.entry!.findIndex( + (entry) => entry.resource?.resourceType === "Patient", + ); + + ( + BundleWithPatientAndContact.entry![patientIndex].resource as Patient + ).contact = [ + { + relationship: [ + { + coding: [ + { + display: "sister", + }, + ], + }, + ], + telecom: [ + { + system: "phone", + value: "+1-615-995-9999", + use: "home", + }, + ], + address: { + use: "home", + line: ["999 Single Court"], + city: "BEVERLY HILLS", + state: "CA", + country: "USA", + postalCode: "90210", + district: "LOS ANGELE", + }, + }, + { + relationship: [ + { + coding: [ + { + display: "brother", + }, + ], + }, + ], + telecom: [ + { + system: "phone", + value: "+1-615-995-1000", + use: "home", + }, + { + system: "fax", + value: "+1-615-995-1001", + use: "home", + }, + ], + }, + ]; + const actual = evaluateEmergencyContact( + BundleWithPatientAndContact, + mappings, + ); + expect(actual).toEqual( + `sister\n999 Single Court\nBEVERLY HILLS, CA\n90210, USA\nHome: 615-995-9999\n\nbrother\nHome: 615-995-1000\nHome Fax: 615-995-1001`, + ); + }); it("should not return empty space when address is not available in", () => { const BundleWithPatientAndContact = JSON.parse( JSON.stringify(BundleWithPatient), diff --git a/containers/fhir-converter/Dockerfile b/containers/fhir-converter/Dockerfile index 5ead6c7681..fc77e36cce 100644 --- a/containers/fhir-converter/Dockerfile +++ b/containers/fhir-converter/Dockerfile @@ -1,7 +1,7 @@ FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build # Download FHIR-Converter -RUN git clone https://github.com/skylight-hq/FHIR-Converter.git --branch v7.0-skylight-8 --single-branch /build/FHIR-Converter +RUN git clone https://github.com/skylight-hq/FHIR-Converter.git --branch v7.0-skylight-9 --single-branch /build/FHIR-Converter WORKDIR /build/FHIR-Converter diff --git a/containers/fhir-converter/tests/integration/__snapshots__/test_FHIR-Converter.ambr b/containers/fhir-converter/tests/integration/__snapshots__/test_FHIR-Converter.ambr index dd4fe97f90..264ada6030 100644 --- a/containers/fhir-converter/tests/integration/__snapshots__/test_FHIR-Converter.ambr +++ b/containers/fhir-converter/tests/integration/__snapshots__/test_FHIR-Converter.ambr @@ -2061,6 +2061,55 @@ }), }), ]), + 'contact': list([ + dict({ + 'relationship': list([ + dict({ + 'coding': list([ + dict({ + 'display': 'Father', + }), + ]), + }), + ]), + 'telecom': list([ + dict({ + 'system': 'phone', + 'use': 'home', + 'value': '+1-777-666-5656', + }), + ]), + }), + dict({ + 'name': dict({ + 'family': 'Bear', + 'given': list([ + 'Mama', + ]), + }), + 'relationship': list([ + dict({ + 'coding': list([ + dict({ + 'display': 'Mother', + }), + ]), + }), + ]), + 'telecom': list([ + dict({ + 'system': 'phone', + 'use': 'home', + 'value': '+1-777-666-5657', + }), + dict({ + 'system': 'phone', + 'use': 'work', + 'value': '+1-777-666-5658', + }), + ]), + }), + ]), 'deceasedBoolean': False, 'extension': list([ dict({ diff --git a/containers/fhir-converter/tests/test_files/snapshot/5370592c-6dd3-4de5-b4f6-8a2f4b0253aa/CDA_eICR.xml b/containers/fhir-converter/tests/test_files/snapshot/5370592c-6dd3-4de5-b4f6-8a2f4b0253aa/CDA_eICR.xml index 2ee7c41757..88c5cf89c7 100644 --- a/containers/fhir-converter/tests/test_files/snapshot/5370592c-6dd3-4de5-b4f6-8a2f4b0253aa/CDA_eICR.xml +++ b/containers/fhir-converter/tests/test_files/snapshot/5370592c-6dd3-4de5-b4f6-8a2f4b0253aa/CDA_eICR.xml @@ -1 +1,3272 @@ -Initial Public Health Case Report37490 SHEFFIELD DRPALMDALECA93550-6865USCASSANDRAARONOFFPRM- Palmdale Regional Medical Center38600 Medical Center DrivePalmdaleCA93551USAJuichungHungPRM- Palmdale Regional Medical Center38600 Medical Center DrivePalmdaleCA93551USA38600 Medical Center DrivePalmdaleCA93551USRajanChahal38600 Medical Center DrivePalmdaleCA93551USAPRM- Palmdale Regional Medical Center38600 Medical Center DrivePalmdaleCA93551USA
PROBLEMS - DIAGNOSES
Problem or DiagnosisProblem Status
Morbid obesity (disorder)Active
Bronchitis (disorder)Active
Hypertensive disorder, systemic arterial (disorder)Active
Disease caused by 2019-nCoVActive
Sleep apnea (finding)Active
ENCOUNTERS
Encounter ReasonDate of Encounter
Outpatient20220407123000+0000
RESULTS
Lab Test NameLab Test Result ValueLab Test Result Date
Hct43.2|http://unitsofmeasure.org|%2022-04-19T17:38:00.000Z
U Beta hCG QlNegative2022-04-19T13:01:00.000Z
Medications Administered
Medication NameMedication Start Date
ergocalciferol (ergocalciferol 1.25 mg (50,000 intl units) oral capsule)20220414172400+0000
lisinopril (lisinopril 10 mg oral tablet)20220414172700+0000
acetaminophen (Ofirmev)20220419153100+0000
ceFAZolin (Ancef/Kefzol)20220419140000+0000
hydrocodone-acetaminophen (Norco 5 mg-325 mg oral tablet)20220419153100+0000
ondansetron (Zofran)20220419153100+0000
pantoprazole (Protonix)20220419153500+0000
prochlorperazine (Compazine)20220419153100+0000
LORazepam20220419151900+0000
ondansetron20220419151900+0000
diphenhydrAMINE20220419151900+0000
fentaNYL20220419151900+0000
fentaNYL20220419151900+0000
morphine20220419151900+0000
morphine20220419151900+0000
naloxone (Narcan)20220419151900+0000
glycopyrrolate (glycopyrrolate (ANES))20220419155600+0000
labetalol (labetalol (ANES))20220419155600+0000
neostigmine (neostigmine (ANES))20220419155600+0000
ondansetron (ondansetron (ANES))20220419155600+0000
ceFAZolin 1 g injection (ANES) 1 gm20220419145100+0000
phenylephrine (phenylephrine (ANES))20220419153300+0000
Lactated Ringers (ANES) 1000 mL20220419142700+0000
propofol20220419152300+0000
bupivacaine-epinephrine20220419151700+0000
dexamethasone (dexAMETHasone 4 mg/mL injectable solution (ANES))20220419150700+0000
esmolol (esmolol (ANES))20220419150700+0000
etomidate (etomidate (ANES))20220419150200+0000
lidocaine (Xylocaine-MPF 1% injectable solution (ANES))20220419150200+0000
propofol (Diprivan 10 mg/mL intravenous emulsion (ANES))20220419150200+0000
rocuronium (rocuronium (ANES))20220419150200+0000
midazolam (midazolam 1 mg/mL injectable solution (ANES))20220419145700+0000
morphine (morphine (ANES))20220419145700+0000
ceFAZolin20220419145000+0000
dexamethasone (dexAMETHasone)20220419145000+0000
esmolol20220419144800+0000
etomidate20220419144800+0000
rocuronium (rocuronium/isoosmotic)20220419144800+0000
lidocaine (lidocaine PF 1% (50mg/5ml))20220419143700+0000
lidocaine (lidocaine PF 2%)20220419143700+0000
morphine20220419143700+0000
propofol20220419143700+0000
midazolam20220419143600+0000
scopolamine (scopolamine 1 mg/72 hr transdermal film, extended release)20220419140000+0000
acetaminophen (Ofirmev)20220419140000+0000
enoxaparin (Lovenox)20220419140000+0000
azithromycin (azithromycin 250 mg oral tablet)20220414172800+0000
benzonatate (benzonatate 100 mg oral capsule)20220414172700+0000
predniSONE (predniSONE 5 mg oral tablet)20220414172600+0000
bupivacaine-epinephrine (bupivacaine-EPINEPHrine 0.25%)20220419133800+0000
IMMUNIZATIONS
Vaccine NameVaccination Date
SARS-COV-2 (COVID-19 MODERNA) mRNA-1273Tue Mar 15 00:00:00 GMT 2022
SARS-COV-2 (COVID-19 MODERNA) mRNA-1273Tue Apr 27 00:00:00 GMT 2021
SARS-COV-2 (COVID-19 MODERNA) mRNA-1273Tue Mar 30 00:00:00 GMT 2021
SOCIAL HISTORY
Social History ObservationSocial History Observation Result
Birth SexF
Travel HistoryCOVID-19 Test Date-
Travel HistoryCOVID-19 Previously tested-Yes, negative result
Travel HistoryTB Risk Score-
Travel HistorySymptoms of TB-No symptoms of TB
Travel HistoryHistory of tuberculosis (situation)-No known history of exposure to TB
Travel HistoryC.diff Screening-No (qualifier value)
Travel HistoryRecent Exposure to Communicable Disease-No (qualifier value)
Travel HistoryPregnancy Status-Negative HCG result
Travel HistoryCOVID-19 Test Date-
Travel HistoryCOVID-19 Previously tested-Yes, result pending
Travel HistoryExposure to severe acute respiratory syndrome coronavirus 2 (event)-Yes (qualifier value)
Travel HistoryMERS-CoV Travel History-Yes (qualifier value)
Travel HistoryTB Risk Score-
Travel HistorySymptoms of TB-No symptoms of TB
Travel HistoryHistory of tuberculosis (situation)-No known history of exposure to TB
Travel HistoryC.diff Screening-No (qualifier value)
Travel HistoryMERS-CoV Symptoms-Cough (finding)
Travel HistoryMRSA Screen Hx MRSA-No (qualifier value)
Travel HistoryMRSA Sceen Dialysis or Venous Access-No (qualifier value)
Travel HistoryMRSA Screen Admit From Non-home Loc-No (qualifier value)
Travel HistoryRecent countries visited (observable entity)-Mexico (geographic location)
Travel HistoryRecent Exposure to Communicable Disease-No (qualifier value)
Travel HistoryRecent countries visited (observable entity)-Mexico (geographic location)
COVID-19 Test Date-COVID-19 Previously tested-Yes, negative resultTB Risk Score-Symptoms of TB-No symptoms of TBHistory of tuberculosis (situation)-No known history of exposure to TBC.diff Screening-No (qualifier value)Recent Exposure to Communicable Disease-No (qualifier value)Pregnancy Status-Negative HCG resultCOVID-19 Test Date-COVID-19 Previously tested-Yes, result pendingExposure to severe acute respiratory syndrome coronavirus 2 (event)-Yes (qualifier value)MERS-CoV Travel History-Yes (qualifier value)TB Risk Score-Symptoms of TB-No symptoms of TBHistory of tuberculosis (situation)-No known history of exposure to TBC.diff Screening-No (qualifier value)MERS-CoV Symptoms-Cough (finding)MRSA Screen Hx MRSA-No (qualifier value)MRSA Sceen Dialysis or Venous Access-No (qualifier value)MRSA Screen Admit From Non-home Loc-No (qualifier value)Recent countries visited (observable entity)-Mexico (geographic location)Recent Exposure to Communicable Disease-No (qualifier value)Recent countries visited (observable entity)-Mexico (geographic location)
Plan of TreatmentNo Plan Of Treatment Information
History of Present Illness
Narrative Text
Unknown History of Present Illness
Reason For Visit
text
SEVERE OBESITY
+ + + + + + + + + Initial Public Health Case Report + + + + + + + + + + 37490 SHEFFIELD DR + PALMDALE + CA + 93550-6865 + US + + + + + + CASSANDRA + ARONOFF + + + + + + + + + + + + + + + + + + + PRM- Palmdale Regional Medical Center + + + + 38600 Medical Center Drive + Palmdale + CA + 93551 + USA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Juichung + Hung + + + + + PRM- Palmdale Regional Medical Center + + + 38600 Medical Center Drive + Palmdale + CA + 93551 + USA + + + + + + + + + 38600 Medical Center Drive + Palmdale + CA + 93551 + US + + + + + Rajan + Chahal + + + + + + + + + + + + + 38600 Medical Center Drive + Palmdale + CA + 93551 + USA + + + + + PRM- Palmdale Regional Medical Center + + + 38600 Medical Center Drive + Palmdale + CA + 93551 + USA + + + + + + + + + +
+ + + + PROBLEMS - DIAGNOSES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Problem or DiagnosisProblem Status
+ Morbid obesity (disorder) + + Active +
+ Bronchitis (disorder) + + Active +
+ Hypertensive disorder, systemic + arterial (disorder) + + Active +
+ Disease caused by 2019-nCoV + + Active +
+ Sleep apnea (finding) + + Active +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + ENCOUNTERS + + + + + + + + + + + + + + +
Encounter ReasonDate of Encounter
+ Outpatient + + 20220407123000+0000 +
+
+ + + + + + + + + + + + + +
+
+ +
+ + + + RESULTS + + + + + + + + + + + + + + + + + + + + + +
Lab Test NameLab Test Result ValueLab Test Result Date
+ Hct + + + 43.2|http://unitsofmeasure.org|% + + + 2022-04-19T17:38:00.000Z +
+ U Beta hCG Ql + + Negative + + + 2022-04-19T13:01:00.000Z +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + Medications Administered + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Medication NameMedication Start Date
+ ergocalciferol (ergocalciferol + 1.25 mg (50,000 intl units) oral capsule) + + 20220414172400+0000 +
+ lisinopril (lisinopril 10 mg oral + tablet) + + 20220414172700+0000 +
+ acetaminophen (Ofirmev) + + 20220419153100+0000 +
+ ceFAZolin (Ancef/Kefzol) + + 20220419140000+0000 +
+ hydrocodone-acetaminophen (Norco 5 + mg-325 mg oral tablet) + + 20220419153100+0000 +
+ ondansetron (Zofran) + + 20220419153100+0000 +
+ pantoprazole (Protonix) + + 20220419153500+0000 +
+ prochlorperazine (Compazine) + + 20220419153100+0000 +
+ LORazepam + + 20220419151900+0000 +
+ ondansetron + + 20220419151900+0000 +
+ diphenhydrAMINE + + 20220419151900+0000 +
+ fentaNYL + + 20220419151900+0000 +
+ fentaNYL + + 20220419151900+0000 +
+ morphine + + 20220419151900+0000 +
+ morphine + + 20220419151900+0000 +
+ naloxone (Narcan) + + 20220419151900+0000 +
+ glycopyrrolate (glycopyrrolate + (ANES)) + + 20220419155600+0000 +
+ labetalol (labetalol (ANES)) + + 20220419155600+0000 +
+ neostigmine (neostigmine (ANES)) + + 20220419155600+0000 +
+ ondansetron (ondansetron (ANES)) + + 20220419155600+0000 +
+ ceFAZolin 1 g injection (ANES) 1 + gm + + 20220419145100+0000 +
+ phenylephrine (phenylephrine + (ANES)) + + 20220419153300+0000 +
+ Lactated Ringers (ANES) 1000 mL + + 20220419142700+0000 +
+ propofol + + 20220419152300+0000 +
+ bupivacaine-epinephrine + + 20220419151700+0000 +
+ dexamethasone (dexAMETHasone 4 + mg/mL injectable solution (ANES)) + + 20220419150700+0000 +
+ esmolol (esmolol (ANES)) + + 20220419150700+0000 +
+ etomidate (etomidate (ANES)) + + 20220419150200+0000 +
+ lidocaine (Xylocaine-MPF 1% + injectable solution (ANES)) + + 20220419150200+0000 +
+ propofol (Diprivan 10 mg/mL + intravenous emulsion (ANES)) + + 20220419150200+0000 +
+ rocuronium (rocuronium (ANES)) + + 20220419150200+0000 +
+ midazolam (midazolam 1 mg/mL + injectable solution (ANES)) + + 20220419145700+0000 +
+ morphine (morphine (ANES)) + + 20220419145700+0000 +
+ ceFAZolin + + 20220419145000+0000 +
+ dexamethasone (dexAMETHasone) + + 20220419145000+0000 +
+ esmolol + + 20220419144800+0000 +
+ etomidate + + 20220419144800+0000 +
+ rocuronium + (rocuronium/isoosmotic) + + 20220419144800+0000 +
+ lidocaine (lidocaine PF 1% + (50mg/5ml)) + + 20220419143700+0000 +
+ lidocaine (lidocaine PF 2%) + + 20220419143700+0000 +
+ morphine + + 20220419143700+0000 +
+ propofol + + 20220419143700+0000 +
+ midazolam + + 20220419143600+0000 +
+ scopolamine (scopolamine 1 mg/72 + hr transdermal film, extended release) + + 20220419140000+0000 +
+ acetaminophen (Ofirmev) + + 20220419140000+0000 +
+ enoxaparin (Lovenox) + + 20220419140000+0000 +
+ azithromycin (azithromycin 250 mg + oral tablet) + + 20220414172800+0000 +
+ benzonatate (benzonatate 100 mg + oral capsule) + + 20220414172700+0000 +
+ predniSONE (predniSONE 5 mg oral + tablet) + + 20220414172600+0000 +
+ bupivacaine-epinephrine + (bupivacaine-EPINEPHrine 0.25%) + + 20220419133800+0000 +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + IMMUNIZATIONS + + + + + + + + + + + + + + + + + + + + + + +
Vaccine NameVaccination Date
+ SARS-COV-2 (COVID-19 MODERNA) + mRNA-1273 + + Tue Mar 15 00:00:00 GMT 2022 +
+ SARS-COV-2 (COVID-19 MODERNA) + mRNA-1273 + + Tue Apr 27 00:00:00 GMT 2021 +
+ SARS-COV-2 (COVID-19 MODERNA) + mRNA-1273 + + Tue Mar 30 00:00:00 GMT 2021 +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + SOCIAL HISTORY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Social History ObservationSocial History Observation Result
+ Birth Sex + + F +
+ Travel History + + COVID-19 Test Date- +
+ Travel History + + COVID-19 Previously + tested-Yes, negative result +
+ Travel History + + TB Risk Score- +
+ Travel History + + Symptoms of TB-No + symptoms of TB +
+ Travel History + + History of tuberculosis + (situation)-No known history of exposure to TB +
+ Travel History + + C.diff Screening-No + (qualifier value) +
+ Travel History + + Recent Exposure to + Communicable Disease-No (qualifier value) +
+ Travel History + + Pregnancy + Status-Negative HCG result +
+ Travel History + + COVID-19 Test Date- +
+ Travel History + + COVID-19 Previously + tested-Yes, result pending +
+ Travel History + + Exposure to severe + acute respiratory syndrome coronavirus 2 (event)-Yes + (qualifier value) +
+ Travel History + + MERS-CoV Travel + History-Yes (qualifier value) +
+ Travel History + + TB Risk Score- +
+ Travel History + + Symptoms of TB-No + symptoms of TB +
+ Travel History + + History of tuberculosis + (situation)-No known history of exposure to TB +
+ Travel History + + C.diff Screening-No + (qualifier value) +
+ Travel History + + MERS-CoV Symptoms-Cough + (finding) +
+ Travel History + + MRSA Screen Hx MRSA-No + (qualifier value) +
+ Travel History + + MRSA Sceen Dialysis or + Venous Access-No (qualifier value) +
+ Travel History + + MRSA Screen Admit From + Non-home Loc-No (qualifier value) +
+ Travel History + + Recent countries + visited (observable entity)-Mexico (geographic location) +
+ Travel History + + Recent Exposure to + Communicable Disease-No (qualifier value) +
+ Travel History + + Recent countries + visited (observable entity)-Mexico (geographic location) +
+
+ + + + + + + + + + + + + + + COVID-19 Test Date- + + + + + + + + + + COVID-19 Previously tested-Yes, negative result + + + + + + + + + + TB Risk Score- + + + + + + + + + + Symptoms of TB-No symptoms of TB + + + + + + + + + + History of tuberculosis (situation)-No known history of exposure + to TB + + + + + + + + + + C.diff Screening-No (qualifier value) + + + + + + + + + + Recent Exposure to Communicable Disease-No (qualifier value) + + + + + + + + + + Pregnancy Status-Negative HCG result + + + + + + + + + + COVID-19 Test Date- + + + + + + + + + + COVID-19 Previously tested-Yes, result pending + + + + + + + + + + Exposure to severe acute respiratory syndrome coronavirus 2 + (event)-Yes (qualifier value) + + + + + + + + + + MERS-CoV Travel History-Yes (qualifier value) + + + + + + + + + + TB Risk Score- + + + + + + + + + + Symptoms of TB-No symptoms of TB + + + + + + + + + + History of tuberculosis (situation)-No known history of exposure + to TB + + + + + + + + + + C.diff Screening-No (qualifier value) + + + + + + + + + + MERS-CoV Symptoms-Cough (finding) + + + + + + + + + + MRSA Screen Hx MRSA-No (qualifier value) + + + + + + + + + + MRSA Sceen Dialysis or Venous Access-No (qualifier value) + + + + + + + + + + MRSA Screen Admit From Non-home Loc-No (qualifier value) + + + + + + + + + + Recent countries visited (observable entity)-Mexico (geographic + location) + + + + + + + + + + Recent Exposure to Communicable Disease-No (qualifier value) + + + + + + + + + + Recent countries visited (observable entity)-Mexico (geographic + location) + + + + +
+
+ +
+ + + + Plan of Treatment + No Plan Of Treatment Information +
+
+ +
+ + + History of Present Illness + + + + + + + + + + + + +
Narrative Text
+ Unknown History of + Present Illness +
+
+
+
+ +
+ + + Reason For Visit + + + + + + + + + + + + +
text
+ SEVERE OBESITY +
+
+
+
+
+
+
From 39be57bc54c8e42d278b5a93be8994f1f1a45282 Mon Sep 17 00:00:00 2001 From: Mary McGrath Date: Fri, 15 Nov 2024 15:56:13 -0500 Subject: [PATCH 2/3] fix: merge default user preferences with stored (#2912) * fix: merge default user preferences with stored * fix: merging, add test --- .../src/app/components/EcrPaginationWrapper.tsx | 3 ++- .../components/EcrPaginationWrapper.test.tsx | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/containers/ecr-viewer/src/app/components/EcrPaginationWrapper.tsx b/containers/ecr-viewer/src/app/components/EcrPaginationWrapper.tsx index 71daf387ab..150d321414 100644 --- a/containers/ecr-viewer/src/app/components/EcrPaginationWrapper.tsx +++ b/containers/ecr-viewer/src/app/components/EcrPaginationWrapper.tsx @@ -42,7 +42,8 @@ const EcrPaginationWrapper = ({ useEffect(() => { const userPreferencesString = localStorage.getItem("userPreferences"); if (userPreferencesString) { - setUserPreferences(JSON.parse(userPreferencesString)); + const storedPrefs = JSON.parse(userPreferencesString); + setUserPreferences({ ...defaultPreferences, ...storedPrefs }); } }, []); diff --git a/containers/ecr-viewer/src/app/tests/components/EcrPaginationWrapper.test.tsx b/containers/ecr-viewer/src/app/tests/components/EcrPaginationWrapper.test.tsx index a7c876e5dc..f5b0efe909 100644 --- a/containers/ecr-viewer/src/app/tests/components/EcrPaginationWrapper.test.tsx +++ b/containers/ecr-viewer/src/app/tests/components/EcrPaginationWrapper.test.tsx @@ -112,6 +112,21 @@ describe("Pagination for EcrPaginationWrapper", () => { expect(mockPush).toHaveBeenLastCalledWith("?itemsPerPage=50&page=1"); }); + it("should load 50 items per page if 50 was previously set and pages wasn't", () => { + const spyLocalStorage = jest.spyOn(Storage.prototype, "getItem"); + spyLocalStorage.mockImplementationOnce(() => + JSON.stringify({ itemsPerPage: 50 }), + ); + render( + +
+
, + ); + + expect(screen.getByText("Showing 1-50 of 100 eCRs")).toBeInTheDocument(); + expect(mockPush).toHaveBeenLastCalledWith("?itemsPerPage=50&page=1"); + }); + it("should display 51-51 on third page", async () => { mockSearchParams.set("page", "3"); render( From 70735e9e2bd2a68d73b23fd8a93519994797bde3 Mon Sep 17 00:00:00 2001 From: Lina Roth Date: Fri, 15 Nov 2024 17:40:21 -0500 Subject: [PATCH 3/3] add mutiple names and addresses (#2911) * add mutiple names and addresses * add some tests * update logic * typo * build error * PR updates * more pr updates * In case there is no official use name --- .../ecr-viewer/src/app/api/fhirPath.yml | 9 +- .../src/app/services/ecrSummaryService.tsx | 2 +- .../app/services/evaluateFhirDataService.ts | 61 +++++-- .../src/app/services/formatService.tsx | 9 + .../src/app/tests/api/loadYamlConfig.test.ts | 4 +- .../tests/assets/BundlePatientMultiple.json | 170 ++++++++++++++++++ .../AccordionContent.test.tsx.snap | 38 ++-- .../services/evaluateFhirDataServices.test.ts | 72 ++++++++ .../view-data/components/PatientBanner.tsx | 2 +- 9 files changed, 326 insertions(+), 41 deletions(-) create mode 100644 containers/ecr-viewer/src/app/tests/assets/BundlePatientMultiple.json diff --git a/containers/ecr-viewer/src/app/api/fhirPath.yml b/containers/ecr-viewer/src/app/api/fhirPath.yml index d5593774bf..1d66f799d5 100644 --- a/containers/ecr-viewer/src/app/api/fhirPath.yml +++ b/containers/ecr-viewer/src/app/api/fhirPath.yml @@ -1,10 +1,5 @@ -patientGivenName: "Bundle.entry.resource.where(resourceType = 'Patient').name.first().given" -patientFamilyName: "Bundle.entry.resource.where(resourceType = 'Patient').name.first().family" -patientStreetAddress: "Bundle.entry.resource.where(resourceType = 'Patient').address.first().line" -patientCity: "Bundle.entry.resource.where(resourceType = 'Patient').address.first().city" -patientState: "Bundle.entry.resource.where(resourceType = 'Patient').address.first().state" -patientZipCode: "Bundle.entry.resource.where(resourceType = 'Patient').address.first().postalCode" -patientCountry: "Bundle.entry.resource.where(resourceType = 'Patient').address.first().country" +patientNameList: "Bundle.entry.resource.where(resourceType = 'Patient').name" +patientAddressList: "Bundle.entry.resource.where(resourceType = 'Patient').address" patientPhoneNumbers: "Bundle.entry.resource.where(resourceType = 'Patient').telecom.where(system = 'phone')" patientEmails: "Bundle.entry.resource.where(resourceType = 'Patient').telecom.where(system = 'email')" patientCounty: "Bundle.entry.resource.where(resourceType = 'Patient').address.first().county" diff --git a/containers/ecr-viewer/src/app/services/ecrSummaryService.tsx b/containers/ecr-viewer/src/app/services/ecrSummaryService.tsx index ee0f6cc258..af54d93ef9 100644 --- a/containers/ecr-viewer/src/app/services/ecrSummaryService.tsx +++ b/containers/ecr-viewer/src/app/services/ecrSummaryService.tsx @@ -55,7 +55,7 @@ export const evaluateEcrSummaryPatientDetails = ( return evaluateData([ { title: "Patient Name", - value: evaluatePatientName(fhirBundle, fhirPathMappings), + value: evaluatePatientName(fhirBundle, fhirPathMappings, false), }, { title: "DOB", diff --git a/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts b/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts index f6241d96bb..450c0cff20 100644 --- a/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts +++ b/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts @@ -30,16 +30,43 @@ import { evaluateTravelHistoryTable } from "./socialHistoryService"; * Evaluates patient name from the FHIR bundle and formats it into structured data for display. * @param fhirBundle - The FHIR bundle containing patient contact info. * @param mappings - The object containing the fhir paths. + * @param isPatientBanner - Whether to format the name for the Patient banner * @returns The formatted patient name */ export const evaluatePatientName = ( fhirBundle: Bundle, mappings: PathMappings, + isPatientBanner: boolean, ) => { - const givenNames = evaluate(fhirBundle, mappings.patientGivenName).join(" "); - const familyName = evaluate(fhirBundle, mappings.patientFamilyName); - - return `${givenNames} ${familyName}`; + const nameList = evaluate(fhirBundle, mappings.patientNameList); + + if (nameList.length > 0) { + if (isPatientBanner) { + const name = nameList.find((name) => name.use === "official"); + if (name) { + return formatName(name.given, name.family, name.prefix, name.suffix); + } else { + return formatName( + nameList[0].given, + nameList[0].family, + nameList[0].prefix, + nameList[0].suffix, + ); + } + } else { + return nameList + .map((name) => { + return formatName( + name.given, + name.family, + name.prefix, + name.suffix, + nameList.length > 1 ? name?.use : undefined, + ); + }) + .join("\n"); + } + } }; /** @@ -94,12 +121,24 @@ export const evaluatePatientAddress = ( fhirBundle: Bundle, mappings: PathMappings, ) => { - const streetAddresses = evaluate(fhirBundle, mappings.patientStreetAddress); - const city = evaluate(fhirBundle, mappings.patientCity)[0]; - const state = evaluate(fhirBundle, mappings.patientState)[0]; - const zipCode = evaluate(fhirBundle, mappings.patientZipCode)[0]; - const country = evaluate(fhirBundle, mappings.patientCountry)[0]; - return formatAddress(streetAddresses, city, state, zipCode, country); + const addresses = evaluate(fhirBundle, mappings.patientAddressList); + + if (addresses.length > 0) { + return addresses + .map((address) => { + return formatAddress( + address.line, + address.city, + address.state, + address.postalCode, + address.country, + addresses.length > 1 ? address?.use : undefined, + ); + }) + .join("\n\n"); + } else { + return ""; + } }; /** @@ -252,7 +291,7 @@ export const evaluateDemographicsData = ( const demographicsData: DisplayDataProps[] = [ { title: "Patient Name", - value: evaluatePatientName(fhirBundle, mappings), + value: evaluatePatientName(fhirBundle, mappings, false), }, { title: "DOB", value: evaluate(fhirBundle, mappings.patientDOB)[0] }, { diff --git a/containers/ecr-viewer/src/app/services/formatService.tsx b/containers/ecr-viewer/src/app/services/formatService.tsx index a9818e2efb..2a2edcb32f 100644 --- a/containers/ecr-viewer/src/app/services/formatService.tsx +++ b/containers/ecr-viewer/src/app/services/formatService.tsx @@ -32,6 +32,7 @@ export interface TableJson { * @param family - Optional string representing family name or surname. * @param [prefix] - Optional array of name prefix(es). * @param [suffix] - Optional array of name suffix(es). + * @param [use] - Optional array of name use(s). * @returns Formatted name. */ export const formatName = ( @@ -39,8 +40,12 @@ export const formatName = ( family?: string, prefix?: string[], suffix?: string[], + use?: string, ) => { const nameArray: string[] = []; + if (use) { + nameArray.push(toSentenceCase(use) + ":"); + } if (prefix) { nameArray.push(...prefix); } @@ -64,6 +69,7 @@ export const formatName = ( * @param state - The state or region name. * @param zipCode - The ZIP code or postal code. * @param country - The country name. + * @param [use] - Optional address use. * @returns The formatted address string. */ export const formatAddress = ( @@ -72,14 +78,17 @@ export const formatAddress = ( state: string | undefined, zipCode: string | undefined, country: string | undefined, + use?: string | undefined, ) => { let address = { + use: use || "", streetAddress: streetAddress || [], cityState: [city, state], zipCodeCountry: [zipCode, country], }; return [ + address.use ? toSentenceCase(address.use) + ":" : "", address.streetAddress.filter(Boolean).join("\n"), address.cityState.filter(Boolean).join(", "), address.zipCodeCountry.filter(Boolean).join(", "), diff --git a/containers/ecr-viewer/src/app/tests/api/loadYamlConfig.test.ts b/containers/ecr-viewer/src/app/tests/api/loadYamlConfig.test.ts index a877547e73..c4851d52c9 100644 --- a/containers/ecr-viewer/src/app/tests/api/loadYamlConfig.test.ts +++ b/containers/ecr-viewer/src/app/tests/api/loadYamlConfig.test.ts @@ -5,8 +5,8 @@ describe("loadYamlConfig", () => { it("returns the yaml config", () => { const config = loadYamlConfig(); expect(Object.keys(config).length).toBeGreaterThan(3); - expect(config["patientGivenName"]).toBe( - "Bundle.entry.resource.where(resourceType = 'Patient').name.first().given", + expect(config["patientNameList"]).toBe( + "Bundle.entry.resource.where(resourceType = 'Patient').name", ); }); }); diff --git a/containers/ecr-viewer/src/app/tests/assets/BundlePatientMultiple.json b/containers/ecr-viewer/src/app/tests/assets/BundlePatientMultiple.json new file mode 100644 index 0000000000..82820a411f --- /dev/null +++ b/containers/ecr-viewer/src/app/tests/assets/BundlePatientMultiple.json @@ -0,0 +1,170 @@ +{ + "resourceType": "Bundle", + "type": "batch", + "entry": [ + { + "fullUrl": "urn:uuid:6b6b3c4c-4884-4a96-b6ab-c46406839cea", + "resource": { + "resourceType": "Patient", + "id": "6b6b3c4c-4884-4a96-b6ab-c46406839cea", + "meta": { + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" + ], + "source": [ + "ecr" + ] + }, + "identifier": [ + { + "system": "urn:oid:2.16.840.1.113883.3.3316.100", + "value": "10308625" + } + ], + "name": [ + { + "use": "official", + "family": "CASTILLO", + "given": [ + "ABEL" + ] + }, + { + "use": "nickname", + "family": "Kid", + "given": [ + "Billy The" + ] + } + ], + "birthDate": "2015-04-15", + "gender": "male", + "extension": [ + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "code": "2054-5", + "display": "Black or African American" + } + }, + { + "url": "detailed", + "valueCoding": { + "code": "2076-8", + "display": "African" + } + }, + { + "url": "text", + "valueString": "Black or African American" + } + ] + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "code": "2135-2", + "display": "Hispanic or Latino" + } + }, + { + "url": "detailed", + "valueCoding": { + "code": "2106-3", + "display": "White" + } + }, + { + "url": "text", + "valueString": "Hispanic or Latino" + } + ] + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex", + "extension": [ + { + "url": "value", + "valueCodeableConcept": { + "coding": [ + { + "code": "M", + "display": "Male", + "system": "urn:oid:2.16.840.1.113883.5.1" + } + ] + } + } + ] + } + ], + "address": [ + { + "use": "home", + "line": [ + "1050 CARPENTER ST" + ], + "city": "EDWARDS", + "state": "CA", + "country": "US", + "postalCode": "93523-2800" + }, + { + "use": "vacation", + "line": [ + "10 Main St." + ], + "city": "City", + "state": "State", + "country": "America", + "postalCode": "7" + }, + { + "use": "work", + "line": [ + "20001 E. Main St." + ], + "city": "New York", + "state": "NY", + "country": "USA", + "postalCode": "10001" + } + ], + "telecom": [ + { + "system": "phone", + "value": "+18184195968", + "use": "home" + }, + { + "system": "email", + "value": "MELLY.C.A.16@GMAIL.COM" + } + ], + "communication": [ + { + "language": { + "coding": [ + { + "system": "urn:ietf:bcp:47", + "code": "en", + "display": "English" + } + ] + } + } + ] + }, + "request": { + "method": "PUT", + "url": "Patient/6b6b3c4c-4884-4a96-b6ab-c46406839cea" + } + } + ] +} diff --git a/containers/ecr-viewer/src/app/tests/components/__snapshots__/AccordionContent.test.tsx.snap b/containers/ecr-viewer/src/app/tests/components/__snapshots__/AccordionContent.test.tsx.snap index d9cc167206..097a6b1618 100644 --- a/containers/ecr-viewer/src/app/tests/components/__snapshots__/AccordionContent.test.tsx.snap +++ b/containers/ecr-viewer/src/app/tests/components/__snapshots__/AccordionContent.test.tsx.snap @@ -43,25 +43,6 @@ exports[`Snapshot test for Accordion Content Given no data, info message for emp
-
-
-
- Patient Name -
-
- -
-
-
-
+
+
+
+ Patient Name +
+
+ No data +
+
+
+
{ expect(actual).toBeUndefined(); }); }); + +describe("Evaluate Patient Address", () => { + it("should return the 1 address", () => { + const actual = evaluatePatientAddress( + BundleWithPatient as unknown as Bundle, + mappings, + ); + expect(actual).toEqual("1050 CARPENTER ST\nEDWARDS, CA\n93523-2800, US"); + }); + it("should return all 3 of the addresses", () => { + const actual = evaluatePatientAddress( + BundlePatientMultiple as unknown as Bundle, + mappings, + ); + expect(actual).toEqual( + "Home:\n" + + "1050 CARPENTER ST\n" + + "EDWARDS, CA\n" + + "93523-2800, US\n" + + "\n" + + "Vacation:\n" + + "10 Main St.\n" + + "City, State\n" + + "7, America\n" + + "\n" + + "Work:\n" + + "20001 E. Main St.\n" + + "New York, NY\n" + + "10001, USA", + ); + }); +}); + +describe("Evaluate Patient Name", () => { + it("should return the 1 name", () => { + const actual = evaluatePatientName( + BundleWithPatient as unknown as Bundle, + mappings, + false, + ); + expect(actual).toEqual("ABEL CASTILLO"); + }); + it("should return all 2 of the names", () => { + const actual = evaluatePatientName( + BundlePatientMultiple as unknown as Bundle, + mappings, + false, + ); + expect(actual).toEqual( + "Official: ABEL CASTILLO\n" + "Nickname: Billy The Kid", + ); + }); + it("should only return the official name for the banner", () => { + const actual = evaluatePatientName( + BundlePatientMultiple as unknown as Bundle, + mappings, + true, + ); + expect(actual).toEqual("ABEL CASTILLO"); + }); + it("should only return the official name for the banner", () => { + const actual = evaluatePatientName( + BundleWithPatient as unknown as Bundle, + mappings, + true, + ); + expect(actual).toEqual("ABEL CASTILLO"); + }); +}); diff --git a/containers/ecr-viewer/src/app/view-data/components/PatientBanner.tsx b/containers/ecr-viewer/src/app/view-data/components/PatientBanner.tsx index 3beed2cda5..e8abf455b4 100644 --- a/containers/ecr-viewer/src/app/view-data/components/PatientBanner.tsx +++ b/containers/ecr-viewer/src/app/view-data/components/PatientBanner.tsx @@ -21,7 +21,7 @@ const PatientBanner = ({ bundle, mappings }: PatientBannerProps) => { return (
- {bundle && mappings ? evaluatePatientName(bundle, mappings) : ""} + {bundle && mappings ? evaluatePatientName(bundle, mappings, true) : ""} {bundle && mappings