Skip to content

Commit

Permalink
feat: Add periods to patient addresses (#3041)
Browse files Browse the repository at this point in the history
* feat: add address dates

* refactor: have formatAddress take address object and config

* test: update tests

* fix: date formatting, clean up defaults

* fix: more type cleanup

* test: unit tests

* fix: get the most current address (not blindly the first)

* fix: finish rename

* Update containers/ecr-viewer/seed-scripts/create-seed-data.py

* fix: moar types

* Update containers/fhir-converter/Dockerfile
  • Loading branch information
mcmcgrath13 authored Dec 12, 2024
1 parent fc8d319 commit 0bbb3c9
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 133 deletions.
6 changes: 1 addition & 5 deletions containers/ecr-viewer/src/app/api/fhirPath.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,12 @@ encounterStartDate: "Bundle.entry.resource.where(resourceType = 'Encounter').per
encounterDiagnosis: "Bundle.entry.resource.where(resourceType = 'Encounter').diagnosis"
encounterType: "Bundle.entry.resource.where(resourceType='Encounter')[0].class.display"
encounterID: "Bundle.entry.resource.where(resourceType='Encounter')[0].identifier"
facilityCity: "Bundle.entry.resource.where(resourceType = 'Location')[0].address.city"
facilityContact: "Bundle.entry.resource.where(resourceType = 'Location')[0].telecom.where(system = 'phone')[0].value"
facilityContactAddress: "Bundle.entry.resource.where(resourceType = 'Encounter')[0].serviceProvider"
facilityCountry: "Bundle.entry.resource.where(resourceType = 'Location')[0].address.country"
facilityLocation: "Bundle.entry.resource.where(resourceType = 'Encounter')[0].location[0].location.reference"
facilityName: "Bundle.entry.resource.where(resourceType = 'Encounter')[0].location[0].location.display"
facilityState: "Bundle.entry.resource.where(resourceType = 'Location')[0].address.state"
facilityStreetAddress: "Bundle.entry.resource.where(resourceType = 'Location')[0].address.line[0]"
facilityAddress: "Bundle.entry.resource.where(resourceType = 'Location')[0].address"
facilityType: "Bundle.entry.resource.where(resourceType = 'Encounter')[0].location[0].extension.where(url = 'http://build.fhir.org/ig/HL7/case-reporting/StructureDefinition-us-ph-location-definitions.html#Location.type').valueCodeableConcept.coding[0].display"
facilityZipCode: "Bundle.entry.resource.where(resourceType = 'Location')[0].address.postalCode"

compositionEncounterRef: "Bundle.entry.resource.where(resourceType = 'Composition').encounter.reference"
encounterIndividualRef: "Encounter.participant.where(type.coding.code = 'ATND').individual.reference"
Expand Down
24 changes: 3 additions & 21 deletions containers/ecr-viewer/src/app/services/ecrMetadataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,7 @@ export const evaluateEcrMetadata = (
},
{
title: "Custodian Address",
value: formatAddress(
custodian?.address?.[0].line,
custodian?.address?.[0].city,
custodian?.address?.[0].state,
custodian?.address?.[0].postalCode,
custodian?.address?.[0].country,
),
value: formatAddress(custodian?.address?.[0]),
},
{
title: "Custodian Contact",
Expand Down Expand Up @@ -233,13 +227,7 @@ const evaluateEcrAuthorDetails = (
{
title: "Author Address",
value: practitioner?.address?.map((address) =>
formatAddress(
address.line,
address.city,
address.state,
address.postalCode,
address.country,
),
formatAddress(address),
),
},
{
Expand All @@ -253,13 +241,7 @@ const evaluateEcrAuthorDetails = (
{
title: "Author Facility Address",
value: organization?.address?.map((address) =>
formatAddress(
address.line,
address.city,
address.state,
address.postalCode,
address.country,
),
formatAddress(address),
),
},
{
Expand Down
36 changes: 33 additions & 3 deletions containers/ecr-viewer/src/app/services/ecrSummaryService.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Bundle, Condition, Extension, Observation } from "fhir/r4";
import { Address, Bundle, Condition, Extension, Observation } from "fhir/r4";
import { evaluateData, PathMappings } from "@/app/view-data/utils/utils";
import {
formatAddress,
formatContactPoint,
formatDate,
formatStartEndDateTime,
} from "@/app/services/formatService";
import { evaluate } from "@/app/view-data/utils/evaluate";
import {
evaluatePatientName,
evaluatePatientAddress,
evaluateEncounterDiagnosis,
} from "./evaluateFhirDataService";
import { DisplayDataProps } from "@/app/view-data/components/DataDisplay";
Expand Down Expand Up @@ -72,7 +72,9 @@ export const evaluateEcrSummaryPatientDetails = (
},
{
title: "Patient Address",
value: evaluatePatientAddress(fhirBundle, fhirPathMappings),
value: findCurrentAddress(
evaluate(fhirBundle, fhirPathMappings.patientAddressList),
),
},
{
title: "Patient Contact",
Expand All @@ -83,6 +85,34 @@ export const evaluateEcrSummaryPatientDetails = (
]);
};

/**
* Find the most current home address.
* @param addresses - List of addresses.
* @returns A string with the formatted current address or an empty string if no address.
*/
export const findCurrentAddress = (addresses: Address[]) => {
// current home address is first pick
let address = addresses.find(
(a) => a.use === "home" && !!a.period?.start && !a.period?.end,
);
// then current address
if (!address) {
address = addresses.find((a) => !!a.period?.start && !a.period?.end);
}

// then home address
if (!address) {
address = addresses.find((a) => a.use == "home");
}

// then first address
if (!address) {
address = addresses[0];
}

return formatAddress(address);
};

/**
* Evaluates and retrieves encounter details from the FHIR bundle using the provided path mappings.
* @param fhirBundle - The FHIR bundle containing patient data.
Expand Down
63 changes: 16 additions & 47 deletions containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Address,
Bundle,
CodeableConcept,
Coding,
Expand Down Expand Up @@ -112,19 +113,18 @@ export const evaluatePatientAddress = (
fhirBundle: Bundle,
mappings: PathMappings,
) => {
const addresses = evaluate(fhirBundle, mappings.patientAddressList);
const addresses = evaluate(
fhirBundle,
mappings.patientAddressList,
) as Address[];

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,
);
return formatAddress(address, {
includeUse: addresses.length > 1,
includePeriod: true,
});
})
.join("\n\n");
} else {
Expand Down Expand Up @@ -390,7 +390,8 @@ export const evaluateFacilityData = (
referenceString = facilityContactAddressRef[0].reference;
}
const facilityContactAddress = referenceString
? evaluateReference(fhirBundle, mappings, referenceString)?.address?.[0]
? (evaluateReference(fhirBundle, mappings, referenceString)
?.address?.[0] as Address)
: undefined;

const facilityData = [
Expand All @@ -401,22 +402,12 @@ export const evaluateFacilityData = (
{
title: "Facility Address",
value: formatAddress(
evaluate(fhirBundle, mappings["facilityStreetAddress"]),
evaluate(fhirBundle, mappings["facilityCity"])[0],
evaluate(fhirBundle, mappings["facilityState"])[0],
evaluate(fhirBundle, mappings["facilityZipCode"])[0],
evaluate(fhirBundle, mappings["facilityCountry"])[0],
evaluate(fhirBundle, mappings["facilityAddress"])[0],
),
},
{
title: "Facility Contact Address",
value: formatAddress(
facilityContactAddress?.line,
facilityContactAddress?.city,
facilityContactAddress?.state,
facilityContactAddress?.postalCode,
facilityContactAddress?.country,
),
value: formatAddress(facilityContactAddress),
},
{
title: "Facility Contact",
Expand Down Expand Up @@ -471,15 +462,7 @@ export const evaluateProviderData = (
},
{
title: "Provider Address",
value: practitioner?.address?.map((address) =>
formatAddress(
address.line,
address.city,
address.state,
address.postalCode,
address.country,
),
),
value: practitioner?.address?.map((address) => formatAddress(address)),
},
{
title: "Provider Contact",
Expand All @@ -491,15 +474,7 @@ export const evaluateProviderData = (
},
{
title: "Provider Facility Address",
value: organization?.address?.map((address) =>
formatAddress(
address.line,
address.city,
address.state,
address.postalCode,
address.country,
),
),
value: organization?.address?.map((address) => formatAddress(address)),
},
{
title: "Provider ID",
Expand Down Expand Up @@ -579,13 +554,7 @@ export const evaluateEmergencyContact = (
.map((contact) => {
const relationship = contact.relationship?.[0].coding?.[0]?.display;

const address = formatAddress(
contact.address?.line,
contact.address?.city,
contact.address?.state,
contact.address?.postalCode,
contact.address?.country,
);
const address = formatAddress(contact.address);

const phoneNumbers = formatContactPoint(contact.telecom);

Expand Down
54 changes: 32 additions & 22 deletions containers/ecr-viewer/src/app/services/formatService.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import { ToolTipElement } from "@/app/view-data/components/ToolTipElement";
import { ContactPoint, HumanName } from "fhir/r4";
import { Address, ContactPoint, HumanName } from "fhir/r4";
import { RenderableNode, safeParse } from "../view-data/utils/utils";
import sanitizeHtml from "sanitize-html";

Expand Down Expand Up @@ -48,36 +48,46 @@ export const formatName = (
return segments.filter(Boolean).join(" ");
};

const DEFAULT_ADDRESS_CONFIG = { includeUse: false, includePeriod: false };
type AddressConfig = { includeUse?: boolean; includePeriod?: boolean };

/**
* Formats an address based on its components.
* @param streetAddress - An array containing street address lines.
* @param city - The city name.
* @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.
* @param address - Object with address parts
* @param address.line - An array containing street address lines.
* @param address.city - The city name.
* @param address.state - The state or region name.
* @param address.postalCode - The ZIP code or postal code.
* @param address.country - The country name.
* @param address.use - Optional address use.
* @param address.period - Optional address use.
* @param config - Configuration object to customize formatting
* @param config.includeUse - Include the use (e.g. `Home:`) on the address if available (default: false)
* @param config.includePeriod - Include the perios (e.g. `Dates: 12/11/2023 - Present`) on the address if available (default: false)
* @returns The formatted address string.
*/
export const formatAddress = (
streetAddress: string[] | undefined,
city: string | undefined,
state: string | undefined,
zipCode: string | undefined,
country: string | undefined,
use?: string | undefined,
{ line, city, state, postalCode, country, use, period }: Address = {},
config: AddressConfig = {},
) => {
let address = {
use: use || "",
streetAddress: streetAddress || [],
cityState: [city, state],
zipCodeCountry: [zipCode, country],
const { includeUse, includePeriod } = {
...DEFAULT_ADDRESS_CONFIG,
...config,
};

const formatDateLine = () => {
const stDt = formatDate(period?.start);
const endDt = formatDate(period?.end);
if (!stDt && !endDt) return false;
return `Dates: ${stDt ?? "Unknown"} - ${endDt ?? "Present"}`;
};

return [
address.use ? toSentenceCase(address.use) + ":" : "",
address.streetAddress.filter(Boolean).join("\n"),
address.cityState.filter(Boolean).join(", "),
address.zipCodeCountry.filter(Boolean).join(", "),
includeUse && use && toSentenceCase(use) + ":",
(line || []).filter(Boolean).join("\n"),
[city, state].filter(Boolean).join(", "),
[postalCode, country].filter(Boolean).join(", "),
includePeriod && formatDateLine(),
]
.filter(Boolean)
.join("\n");
Expand Down
8 changes: 1 addition & 7 deletions containers/ecr-viewer/src/app/services/labsService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -560,13 +560,7 @@ export const evaluateLabOrganizationData = (
matchingOrg = findIdenticalOrg(orgMappings, matchingOrg);
}
const orgAddress = matchingOrg?.address?.[0];
const formattedAddress = formatAddress(
orgAddress?.line,
orgAddress?.city,
orgAddress?.state,
orgAddress?.postalCode,
orgAddress?.country,
);
const formattedAddress = formatAddress(orgAddress);

const contactInfo = formatPhoneNumber(matchingOrg?.telecom?.[0].value);
const name = matchingOrg?.name ?? "";
Expand Down
54 changes: 54 additions & 0 deletions containers/ecr-viewer/src/app/tests/components/EcrSummary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { axe } from "jest-axe";
import EcrSummary, {
ConditionSummary,
} from "../../view-data/components/EcrSummary";
import { findCurrentAddress } from "@/app/services/ecrSummaryService";

describe("EcrSummary", () => {
const patientDetails = [
Expand Down Expand Up @@ -186,3 +187,56 @@ describe("EcrSummary", () => {
expect(screen.getByText("2 CONDITIONS FOUND"));
});
});

describe("findCurrentAddress", () => {
const base = {
line: ["123 Main St"],
};

it("should return empty when no addresses available", () => {
const actual = findCurrentAddress([]);

expect(actual).toEqual("");
});

it("should return first address when no use or period", () => {
const actual = findCurrentAddress([
{ ...base, city: "1" },
{ ...base, city: "2" },
]);

expect(actual).toEqual("123 Main St\n1");
});

it("should return first home address when no current period", () => {
const actual = findCurrentAddress([
{ ...base, use: "work", city: "1" },
{ ...base, use: "home", city: "2" },
{ ...base, use: "home", city: "3" },
{
...base,
use: "home",
city: "3",
period: { start: "2020-03-01", end: "2020-04-01" },
},
]);

expect(actual).toEqual("123 Main St\n2");
});

it("should return current home address", () => {
const actual = findCurrentAddress([
{ ...base, use: "work", city: "1" },
{ ...base, use: "home", city: "2" },
{ ...base, use: "home", city: "3", period: { start: "2024-03-13" } },
{
...base,
use: "home",
city: "4",
period: { start: "2024-03-10", end: "2024-03-12" },
},
]);

expect(actual).toEqual("123 Main St\n3");
});
});
Loading

0 comments on commit 0bbb3c9

Please sign in to comment.