Skip to content

Commit

Permalink
Merge pull request #2224 from metriport/1603-add-immunizations-section
Browse files Browse the repository at this point in the history
1603 add immunizations and vital signs sections
  • Loading branch information
RamilGaripov committed Jun 20, 2024
2 parents c4f9964 + c86d5bc commit 4e4970b
Show file tree
Hide file tree
Showing 25 changed files with 1,670 additions and 26 deletions.
5 changes: 5 additions & 0 deletions packages/core/src/external/fhir/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
DocumentReference,
Encounter,
Extension,
Immunization,
Location,
Medication,
MedicationStatement,
Expand Down Expand Up @@ -165,6 +166,10 @@ export function isEncounter(resource: Resource | undefined): resource is Encount
return resource?.resourceType === "Encounter";
}

export function isImmunization(resource: Resource | undefined): resource is Immunization {
return resource?.resourceType === "Immunization";
}

export function isObservation(resource: Resource | undefined): resource is Observation {
return resource?.resourceType === "Observation";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Bundle } from "@medplum/fhirtypes";
import { buildAllergies } from "../components/allergies";
import { buildMedications } from "../components/medications";
import { buildEncounters } from "../components/encounters";
import { buildImmunizations } from "../components/immunizations";
import { buildMedications } from "../components/medications";
import { buildMentalStatus } from "../components/mental-status";
import { buildProblems } from "../components/problems";
import { buildResult } from "../components/results";
import { buildSocialHistory } from "../components/social-history";
import { buildVitalSigns } from "../components/vital-signs";

export function buildStructuredBody(fhirBundle: Bundle): unknown {
const structuredBodySections = [
Expand All @@ -16,6 +18,8 @@ export function buildStructuredBody(fhirBundle: Bundle): unknown {
buildProblems(fhirBundle),
buildAllergies(fhirBundle),
buildEncounters(fhirBundle),
buildImmunizations(fhirBundle),
buildVitalSigns(fhirBundle),
];

return {
Expand Down
48 changes: 44 additions & 4 deletions packages/core/src/fhir-to-cda/cda-templates/commons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
import utc from "dayjs/plugin/utc";
import {
AssignedEntity,
CDAOriginalText,
CdaOriginalText,
CdaAddress,
CdaAddressUse,
CdaCodeCe,
Expand Down Expand Up @@ -43,6 +43,7 @@ import {
placeholderOrgOid,
providerTaxonomy,
snomedSystemCode,
vaccineAdministeredCodeSet,
} from "./constants";

dayjs.extend(localizedFormat);
Expand All @@ -56,6 +57,8 @@ CODING_MAP.set("http:https://www.ama-assn.org/go/cpt", amaAssnSystemCode);
CODING_MAP.set("http:https://fdasis.nlm.nih.gov", fdasisSystemCode);
CODING_MAP.set("http:https://terminology.hl7.org/codesystem/v3-actcode", hl7ActCode);
CODING_MAP.set("http:https://nucc.org/provider-taxonomy", providerTaxonomy);
CODING_MAP.set("http:https://hl7.org/fhir/sid/cvx", vaccineAdministeredCodeSet);

CODING_MAP.set("icd-10", icd10SystemCode);

export const TIMESTAMP_CLEANUP_REGEX = /-|T|:|\.\d+Z$/g;
Expand Down Expand Up @@ -113,7 +116,7 @@ export function buildCodeCe({
return codeObject;
}

export function buildOriginalTextReference(value: string): CDAOriginalText {
export function buildOriginalTextReference(value: string): CdaOriginalText {
return {
reference: {
_value: value,
Expand Down Expand Up @@ -459,10 +462,10 @@ export function getTextFromCode(code: CodeableConcept | undefined): string {
}

export function getDisplaysFromCodeableConcepts(
concepts: CodeableConcept[] | undefined
concepts: CodeableConcept | CodeableConcept[] | undefined
): string | undefined {
if (!concepts) return undefined;
return concepts
return toArray(concepts)
.map(concept => {
const code = buildCodeCeFromCoding(concept.coding);
if (code?._displayName) return code._displayName.trim();
Expand Down Expand Up @@ -495,13 +498,45 @@ export function buildPerformer(practitioners: Practitioner[] | undefined): Assig
family: p.name?.flatMap(n => n.family).join(", "),
},
},
representedOrganization: {
_classCode: "ORG",
name: {
"#text": "",
},
addr: buildAddress(p.address),
telecom: buildTelecom(p.telecom),
},
},
} || []
);
}) || []
);
}

export function buildPerformerFromLocation(
location: Location | undefined
): AssignedEntity | undefined {
if (!location) return undefined;
return {
assignedEntity: {
id: buildInstanceIdentifier({
root: placeholderOrgOid,
extension: location.id,
}),
addr: buildAddress(location.address),
telecom: buildTelecom(location.telecom),
representedOrganization: {
_classCode: "ORG",
name: {
"#text": "",
},
addr: buildAddress(location.address),
telecom: buildTelecom(location.telecom),
},
},
};
}

export function buildParticipant(locations: Location[] | undefined): Participant[] | undefined {
if (!locations) return undefined;

Expand Down Expand Up @@ -535,3 +570,8 @@ export function buildParticipant(locations: Location[] | undefined): Participant
return participant;
});
}

export function buildAddressText(address: Address | undefined): string | undefined {
if (!address) return undefined;
return `${address.line?.join(", ")}, ${address.city}, ${address.state} ${address.postalCode}`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { faker } from "@faker-js/faker";
import { Bundle, Immunization, Location } from "@medplum/fhirtypes";
import _ from "lodash";
import path from "path";
import { removeEmptyFields } from "../../clinical-document/clinical-document";
import { xmlBuilder } from "../../clinical-document/shared";
import { buildImmunizations } from "../immunizations";
import { immunizationFlu } from "./immunizations-examples";
import { location1 } from "./encounter-examples";
import { makeImmunization } from "./make-immunization";
import { createEmptyBundle, getXmlContentFromFile } from "./shared";
import { makeLocation } from "./make-encounter";

let immunizationId: string;
let locationId: string;
let bundle: Bundle;
let immunization: Immunization;
let location: Location;

beforeEach(() => {
immunizationId = faker.string.uuid();
locationId = faker.string.uuid();
immunization = makeImmunization({ ...immunizationFlu }, { imm: immunizationId, loc: locationId });
location = makeLocation({ ...location1, id: locationId });
bundle = createEmptyBundle();
});

describe("buildImmunizations", () => {
it("correctly maps a single Immunization", () => {
bundle.entry?.push({ resource: immunization });
bundle.entry?.push({ resource: location });

const filePath = path.join(__dirname, "./xmls/immunization-section-single-entry.xml");
const xmlTemplate = _.template(getXmlContentFromFile(filePath));
const params = {
immunizationId,
locationId,
};
const xmlContent = xmlTemplate(params);
const res = buildImmunizations(bundle);
const cleanedJsonObj = removeEmptyFields(res);
const xmlRes = xmlBuilder.build(cleanedJsonObj);
expect(xmlRes).toEqual(xmlContent);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Immunization } from "@medplum/fhirtypes";

export const immunizationFlu: Partial<Immunization> = {
status: "completed",
vaccineCode: {
coding: [
{
system: "http:https://hl7.org/fhir/sid/cvx",
code: "140",
display: "Influenza, seasonal, injectable, preservative free",
},
],
text: "Influenza, seasonal, injectable, preservative free",
},
patient: {
reference: "Patient/018ef2b1-a952-7a43-9124-7a86f10b9d78",
},
encounter: {
reference: "Encounter/90170bdf-3029-49d9-ba31-48c942bf3799",
},
occurrenceDateTime: "2013-09-19T18:14:30-07:00",
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export function makeComposition(ids: { enc: string; pract: string }): Compositio
},
date: "2024-03-06T21:22:21.000Z",
title: "Encounter Summary",
event: [{ period: { start: "2012-07-23T16:45:00.000Z" } }],
section: [
{
title: "Results",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Immunization } from "@medplum/fhirtypes";
import { makeBaseDomain, makeSubjectReference } from "./shared";

export function makeImmunization(
params: Partial<Immunization> = {},
ids: { imm: string; loc: string }
): Immunization {
return {
...makeBaseDomain(),
...makeSubjectReference(),
resourceType: "Immunization",
...params,
id: ids.imm,
location: { reference: `Location/${ids.loc}` },
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export const observationMentalStatus: Partial<Observation> = {
text: "Low risk",
},
effectiveDateTime: "2012-07-23T17:16:00.324-07:00",
issued: "2012-07-23T17:16:00.324-07:00",
interpretation: [
{
coding: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Observation } from "@medplum/fhirtypes";

export const vitalSignObservationTemplate: Partial<Observation> = {
resourceType: "Observation",
status: "final",
category: [
{
coding: [
{
system: "http:https://terminology.hl7.org/CodeSystem/observation-category",
code: "vital-signs",
display: "Vital signs",
},
],
},
],
code: {
coding: [
{
system: "http:https://loinc.org",
code: "72514-3",
display: "Pain severity - 0-10 verbal numeric rating [Score] - Reported",
},
],
text: "Pain severity - 0-10 verbal numeric rating [Score] - Reported",
},
effectiveDateTime: "2013-09-19T18:14:30-07:00",
valueQuantity: {
value: 0,
unit: "{score}",
system: "http:https://unitsofmeasure.org",
code: "{score}",
},
};

export const obsHeartRate: Partial<Observation> = {
code: {
text: "Heart rate",
coding: [{ code: "8867-4", system: "http:https://loinc.org" }],
},
effectiveDateTime: "2013-09-19T18:14:30-07:00",
valueQuantity: { value: 92, unit: "/min", system: "http:https://unitsofmeasure.org" },
};

export const obsTemperature: Partial<Observation> = {
code: {
text: "Body temperature",
coding: [{ code: "8310-5", system: "http:https://loinc.org" }],
},
effectiveDateTime: "2013-09-19T18:14:30-07:00",
valueQuantity: { value: 36.72, unit: "Cel", system: "http:https://unitsofmeasure.org" },
};

export const obsRespiratoryRate: Partial<Observation> = {
code: {
text: "Respiratory rate",
coding: [{ code: "9279-1", system: "http:https://loinc.org" }],
},
effectiveDateTime: "2013-09-19T18:14:30-07:00",
valueQuantity: { value: 16, unit: "/min", system: "http:https://unitsofmeasure.org" },
};

export const obsWeight: Partial<Observation> = {
code: {
text: "Body weight",
coding: [{ code: "29463-7", system: "http:https://loinc.org" }],
},
effectiveDateTime: "2013-09-02T18:14:30-07:00",
valueQuantity: { value: 63.504, unit: "kg", system: "http:https://unitsofmeasure.org" },
};

export const obsSystolic: Partial<Observation> = {
code: {
text: "Systolic blood pressure",
coding: [{ code: "8480-6", system: "http:https://loinc.org" }],
},
effectiveDateTime: "2013-09-02T18:14:30-07:00",
valueQuantity: { value: 104, unit: "mm[Hg]", system: "http:https://unitsofmeasure.org" },
};

export const obsDiastolic: Partial<Observation> = {
code: {
text: "Diastolic blood pressure",
coding: [{ code: "8462-4", system: "http:https://loinc.org" }],
},
effectiveDateTime: "2013-09-02T18:14:30-07:00",
valueQuantity: { value: 78, unit: "mm[Hg]", system: "http:https://unitsofmeasure.org" },
};

export const obsHeight: Partial<Observation> = {
code: {
coding: [
{
system: "http:https://loinc.org",
code: "8302-2",
display: "Body Height",
},
],
text: "Body Height",
},
effectiveDateTime: "2013-09-02T18:14:30-07:00",
valueQuantity: {
value: 172.3,
unit: "cm",
system: "http:https://unitsofmeasure.org",
code: "cm",
},
};
Loading

0 comments on commit 4e4970b

Please sign in to comment.