Skip to content

feat: Public Health Emergency Outbreak Info field #680

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ describe("Snapshot test for Procedures (Treatment Details)", () => {
<ClinicalInfo
clinicalNotes={[]}
activeProblemsDetails={[]}
emergencyOutbreakInfo={[]}
vitalData={[]}
reasonForVisitDetails={[]}
immunizationsDetails={[]}
Expand Down Expand Up @@ -145,6 +146,7 @@ describe("Snapshot test for Clinical Notes", () => {
<ClinicalInfo
clinicalNotes={clinicalNotes}
activeProblemsDetails={[]}
emergencyOutbreakInfo={[]}
vitalData={[]}
reasonForVisitDetails={[]}
immunizationsDetails={[]}
Expand Down Expand Up @@ -212,6 +214,7 @@ describe("Snapshot test for Clinical Notes", () => {
<ClinicalInfo
clinicalNotes={clinicalNotes}
activeProblemsDetails={[]}
emergencyOutbreakInfo={[]}
vitalData={[]}
reasonForVisitDetails={[]}
immunizationsDetails={[]}
Expand All @@ -235,13 +238,15 @@ describe("Check that Clinical Info components render given FHIR bundle", () => {
const testReasonForVisitData =
testClinicalData.reasonForVisitDetails.availableData;
const testTreatmentData = testClinicalData.treatmentData.availableData;
const testOutbreakInfo = testClinicalData.emergencyOutbreakInfo.availableData;

it("eCR Viewer renders immunization table given FHIR bundle with immunization info", () => {
const clinicalInfo = render(
<ClinicalInfo
immunizationsDetails={testImmunizationsData}
reasonForVisitDetails={[]}
activeProblemsDetails={[]}
emergencyOutbreakInfo={[]}
vitalData={[]}
treatmentData={[]}
clinicalNotes={[]}
Expand All @@ -266,6 +271,7 @@ describe("Check that Clinical Info components render given FHIR bundle", () => {
immunizationsDetails={[]}
reasonForVisitDetails={[]}
activeProblemsDetails={testActiveProblemsData}
emergencyOutbreakInfo={[]}
vitalData={[]}
treatmentData={[]}
clinicalNotes={[]}
Expand All @@ -288,6 +294,7 @@ describe("Check that Clinical Info components render given FHIR bundle", () => {
immunizationsDetails={[]}
reasonForVisitDetails={[]}
activeProblemsDetails={[]}
emergencyOutbreakInfo={[]}
vitalData={testVitalSignsData}
treatmentData={[]}
clinicalNotes={[]}
Expand Down Expand Up @@ -326,6 +333,7 @@ describe("Check that Clinical Info components render given FHIR bundle", () => {
immunizationsDetails={[]}
reasonForVisitDetails={testReasonForVisitData}
activeProblemsDetails={[]}
emergencyOutbreakInfo={[]}
vitalData={[]}
treatmentData={[]}
clinicalNotes={[]}
Expand All @@ -343,6 +351,7 @@ describe("Check that Clinical Info components render given FHIR bundle", () => {
immunizationsDetails={[]}
reasonForVisitDetails={[]}
activeProblemsDetails={[]}
emergencyOutbreakInfo={[]}
vitalData={[]}
treatmentData={testTreatmentData}
clinicalNotes={[]}
Expand All @@ -358,12 +367,31 @@ describe("Check that Clinical Info components render given FHIR bundle", () => {
expect(expectedTable.length).toEqual(4);
});

it("eCR Viewer renders emergency outbreak info given FHIR bundle with emergency outbreak info", () => {
const clinicalInfo = render(
<ClinicalInfo
immunizationsDetails={[]}
reasonForVisitDetails={[]}
activeProblemsDetails={[]}
emergencyOutbreakInfo={testOutbreakInfo}
vitalData={[]}
treatmentData={[]}
clinicalNotes={[]}
/>,
);
const expectedEmergencyOutbreakElement = clinicalInfo.getByTestId(
"emergency-outbreak-info",
);
expect(expectedEmergencyOutbreakElement).toBeInTheDocument();
});

it("eCR Viewer renders all Clinical Info sections", () => {
const clinicalInfo = render(
<ClinicalInfo
immunizationsDetails={testImmunizationsData}
reasonForVisitDetails={testReasonForVisitData}
activeProblemsDetails={testActiveProblemsData}
emergencyOutbreakInfo={[]}
vitalData={testVitalSignsData}
treatmentData={testTreatmentData}
clinicalNotes={[]}
Expand Down
19 changes: 11 additions & 8 deletions containers/ecr-viewer/src/app/utils/evaluate/fhir-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export type PathTypes = {
clinicalReasonForVisit: ValueX;
patientVitalSigns: Observation;
vitalSignType: CodeableConcept;
vitalSignValue: ValueX;
value: ValueX;
vitalSignDateTime: ValueX;
resolve: unknown;
activeProblems: Condition;
Expand All @@ -106,6 +106,7 @@ export type PathTypes = {
activeProblemsOnsetAge: ValueX;
activeProblemsComments: string;
historyOfPresentIllness: string;
emergencyOutbreakInfo: Observation;
planOfTreatment: string;
plannedProcedures: CarePlanActivity;
plannedProcedureName: string;
Expand Down Expand Up @@ -393,12 +394,6 @@ const _fhirPathMappings: { [K in FhirPathKeys]: Omit<FhirPath<K>, "name"> } = {
path: "Bundle.entry.resource.where(meta.profile = 'http://hl7.org/fhir/us/ecr/StructureDefinition/rr-reportability-information-observation')",
},

// Clinical Data
clinicalReasonForVisit: {
type: "ValueX",
path: "Bundle.entry.resource.section.where(title.lower() = 'reason for visit')[0].extension[0].value",
},

// Vitals
patientVitalSigns: {
type: "Observation",
Expand All @@ -408,7 +403,7 @@ const _fhirPathMappings: { [K in FhirPathKeys]: Omit<FhirPath<K>, "name"> } = {
type: "CodeableConcept",
path: "code",
},
vitalSignValue: {
value: {
type: "ValueX",
path: "value",
},
Expand All @@ -423,6 +418,10 @@ const _fhirPathMappings: { [K in FhirPathKeys]: Omit<FhirPath<K>, "name"> } = {
},

// Clinical Info
clinicalReasonForVisit: {
type: "ValueX",
path: "Bundle.entry.resource.section.where(title.lower() = 'reason for visit')[0].extension[0].value",
},
activeProblems: {
type: "Condition",
path: "Bundle.entry.resource.where(resourceType = 'Condition').where(category.coding.code = 'problem-item-list')",
Expand All @@ -438,6 +437,10 @@ const _fhirPathMappings: { [K in FhirPathKeys]: Omit<FhirPath<K>, "name"> } = {
type: "string",
path: "Bundle.entry.resource.where(resourceType = 'Composition').section.where(code.coding.code = '10164-2').text.`div`.first()",
},
emergencyOutbreakInfo: {
type: "Observation",
path: "Bundle.entry.resource.where(resourceType = 'Observation').where(meta.profile = 'http://hl7.org/fhir/us/ecr/StructureDefinition/us-ph-emergency-outbreak-information')",
},

// Treatment Details
planOfTreatment: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DataDisplay, DataTableDisplay, DisplayDataProps } from "./DataDisplay";
interface ClinicalProps {
reasonForVisitDetails: DisplayDataProps[];
activeProblemsDetails: DisplayDataProps[];
emergencyOutbreakInfo: DisplayDataProps[];
vitalData: DisplayDataProps[];
immunizationsDetails: DisplayDataProps[];
treatmentData: DisplayDataProps[];
Expand Down Expand Up @@ -62,11 +63,17 @@ const ClinicalNotes = ({ details }: { details: DisplayDataProps[] }) => {
const SymptomsAndProblems = ({
symptoms,
problems,
emergencyOutbreakInfo,
}: {
symptoms: DisplayDataProps[];
problems: DisplayDataProps[];
emergencyOutbreakInfo: DisplayDataProps[];
}) => {
if (symptoms?.length > 0 || problems?.length > 0) {
if (
symptoms?.length > 0 ||
problems?.length > 0 ||
emergencyOutbreakInfo?.length > 0
) {
return (
<AccordionSubSection title="Symptoms and Problems">
<div data-testid="reason-for-visit">
Expand All @@ -80,6 +87,11 @@ const SymptomsAndProblems = ({
className="table-clinical-problems"
/>
</div>
<div data-testid="emergency-outbreak-info">
{emergencyOutbreakInfo.map((item, index) => (
<DataDisplay item={item} key={index} />
))}
</div>
</AccordionSubSection>
);
}
Expand Down Expand Up @@ -138,6 +150,7 @@ const VitalDetails = ({ details }: { details: DisplayDataProps[] }) => {
* @param props - Props containing clinical information.
* @param props.reasonForVisitDetails - The details of the reason for visit.
* @param props.activeProblemsDetails - The details of active problems.
* @param props.emergencyOutbreakInfo - The details of emergency outbreak information.
* @param props.immunizationsDetails - The details of immunizations.
* @param props.vitalData - The vital signs data.
* @param props.treatmentData - The details of treatments.
Expand All @@ -147,6 +160,7 @@ const VitalDetails = ({ details }: { details: DisplayDataProps[] }) => {
export const ClinicalInfo = ({
reasonForVisitDetails,
activeProblemsDetails,
emergencyOutbreakInfo,
immunizationsDetails,
vitalData,
treatmentData,
Expand All @@ -158,6 +172,7 @@ export const ClinicalInfo = ({
<SymptomsAndProblems
symptoms={reasonForVisitDetails}
problems={activeProblemsDetails}
emergencyOutbreakInfo={emergencyOutbreakInfo}
/>
<TreatmentDetails details={treatmentData} />
<ImmunizationsDetails details={immunizationsDetails} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const getEcrDocumentAccordionItems = (
encounterData.unavailableData,
clinicalData.reasonForVisitDetails.unavailableData,
clinicalData.activeProblemsDetails.unavailableData,
clinicalData.emergencyOutbreakInfo.unavailableData,
providerData.unavailableData,
clinicalData.vitalData.unavailableData,
clinicalData.immunizationsDetails.unavailableData,
Expand Down Expand Up @@ -119,6 +120,9 @@ export const getEcrDocumentAccordionItems = (
activeProblemsDetails={
clinicalData.activeProblemsDetails.availableData
}
emergencyOutbreakInfo={
clinicalData.emergencyOutbreakInfo.availableData
}
vitalData={clinicalData.vitalData.availableData}
immunizationsDetails={clinicalData.immunizationsDetails.availableData}
treatmentData={clinicalData.treatmentData.availableData}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ export const evaluateClinicalData = (fhirBundle: Bundle) => {
},
];

const emergencyOutbreakInfo: DisplayDataProps[] = [
{
title: "Emergency Outbreak Info",
value: evaluateOutbreakInfo(fhirBundle),
},
];

const administeredMedication = evaluateAdministeredMedication(fhirBundle);

const treatmentData: DisplayDataProps[] = [
Expand Down Expand Up @@ -122,6 +129,7 @@ export const evaluateClinicalData = (fhirBundle: Bundle) => {
clinicalNotes: evaluateData(clinicalNotes),
reasonForVisitDetails: evaluateData(reasonForVisitData),
activeProblemsDetails: evaluateData(activeProblemsTableData),
emergencyOutbreakInfo: evaluateData(emergencyOutbreakInfo),
treatmentData: evaluateData(treatmentData),
vitalData: evaluateData(vitalData),
immunizationsDetails: evaluateData(immunizationsData),
Expand Down Expand Up @@ -420,7 +428,7 @@ export const returnVitalsTable = (fhirBundle: Bundle) => {
infoPath: "vitalSignType",
applyToValue: toSentenceCase,
},
{ columnName: "Result", infoPath: "vitalSignValue" },
{ columnName: "Result", infoPath: "value" },
{
columnName: "Date/Time",
infoPath: "vitalSignDateTime",
Expand All @@ -438,3 +446,37 @@ export const returnVitalsTable = (fhirBundle: Bundle) => {
/>
);
};

const evaluateOutbreakInfo = (fhirBundle: Bundle): string => {
const outbreakInfos = evaluateAll(
fhirBundle,
fhirPathMappings.emergencyOutbreakInfo,
);

return outbreakInfos
.map((outbreakInfo) => {
const lines = [];

if (outbreakInfo.effectiveDateTime) {
lines.push(
"Date/Time: " + formatDateTime(outbreakInfo.effectiveDateTime),
);
} else if (outbreakInfo.effectivePeriod) {
lines.push(formatStartEndDate(outbreakInfo.effectivePeriod));
}

// Get the first display value from the coding array
const coding = outbreakInfo.code?.coding?.find((c) => c.display);
if (coding?.display) {
lines.push("Type: " + coding.display);
}

const value = evaluateValue(outbreakInfo, fhirPathMappings.value);
if (value) {
lines.push("Result: " + value);
}

return lines.join("\n");
})
.join("\n\n");
};
3 changes: 2 additions & 1 deletion containers/fhir-converter/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build

# Download FHIR-Converter
RUN git clone https://github.com/CDCgov/dibbs-FHIR-Converter.git --branch v7.0-3 --depth 1 /build/FHIR-Converter
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need to be reverted once the FHIR Converter has been updated and a new release made.

# RUN git clone https://github.com/CDCgov/dibbs-FHIR-Converter.git --branch v7.0-3 --depth 1 /build/FHIR-Converter
RUN git clone https://github.com/CDCgov/dibbs-FHIR-Converter.git --branch josh/592/public-health-emergency --depth 1 /build/FHIR-Converter

WORKDIR /build/FHIR-Converter

Expand Down
41 changes: 41 additions & 0 deletions test-data/fhir/BundleClinicalInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,47 @@
"method": "PUT",
"url": "MedicationAdministration/367be6b3-75f2-2053-63e4-f0a6f1d6bff1"
}
},
{
"fullUrl": "urn:uuid:26d05bcc-1b0f-fc8a-a85e-4c6c6512478f",
"resource": {
"resourceType": "Observation",
"id": "26d05bcc-1b0f-fc8a-a85e-4c6c6512478f",
"meta": {
"profile": [
"http://hl7.org/fhir/us/ecr/StructureDefinition/us-ph-emergency-outbreak-information"
],
"source": "ecr"
},
"identifier": [
{
"system": "urn:ietf:rfc:3986",
"value": "urn:uuid:ab1791b0-5c71-11db-b0de-0800200c9a54"
}
],
"status": "final",
"code": {
"coding": [
{
"display": "Employee desk distance from mail sorter"
}
]
},
"subject": {
"reference": "Patient/f35db4f9-0d60-4d3d-ac02-75affb3f2059"
},
"valueQuantity": {
"value": 2,
"unit": "m"
},
"effectivePeriod": {
"start": "2020-11-01"
}
},
"request": {
"method": "PUT",
"url": "Observation/26d05bcc-1b0f-fc8a-a85e-4c6c6512478f"
}
}
]
}