From 70735e9e2bd2a68d73b23fd8a93519994797bde3 Mon Sep 17 00:00:00 2001 From: Lina Roth Date: Fri, 15 Nov 2024 17:40:21 -0500 Subject: [PATCH] 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