Skip to content
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

SNOMED Hydrating, Filtering, Special MR Generation #1648

Draft
wants to merge 19 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
feat(snomed): medications dates good pray
Refs: #1442
  • Loading branch information
jonahkaye committed Apr 19, 2024
commit e7eb48f803e7b822d12d85ec5981fa8fdefd23c2
110 changes: 71 additions & 39 deletions packages/core/src/external/aws/lambda-logic/bundle-to-html-snomed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const bundleToHtml = (fhirBundle: Bundle): string => {
patient,
medications,
medicationAdministrations,
medicationStatements,
conditions,
allergies,
procedures,
Expand Down Expand Up @@ -219,7 +220,8 @@ export const bundleToHtml = (fhirBundle: Bundle): string => {
${createMRHeader(patient)}
<div class="divider"></div>
<div id="mr-sections">
${createConditionLinkedMedicationSection(medications, medicationAdministrations)}
${createDischargeMedicationsSection(medications, medicationAdministrations)}
${createMedicationSection(medications, medicationStatements)}
${createConditionSection(conditions, encounters)}
${createAllergySection(allergies)}
${createProcedureSection(procedures)}
Expand Down Expand Up @@ -744,7 +746,7 @@ function createOrganiztionField(
return organization?.name ? `<p>Facility: ${organization.name}</p>` : "";
}

function createConditionLinkedMedicationSection(
function createDischargeMedicationsSection(
medications: Medication[],
medicationAdministrations: MedicationAdministration[]
) {
Expand Down Expand Up @@ -779,17 +781,35 @@ function createConditionLinkedMedicationSection(
);
});

const medicationSection = createMedLinkedConditionSectionInMedications(
const activeMedications = removeDuplicate.filter(
medicationAdministration => medicationAdministration.status !== "stopped"
);

const oneYearAgo = dayjs().subtract(1, "year");

const activeMedicationsWithinLastYear = activeMedications.filter(medicationAdministration => {
const start = medicationAdministration.effectivePeriod?.start;
const end = medicationAdministration.effectivePeriod?.end;
const medicationStart = start ? dayjs(start) : undefined;
const medicationEnd = end ? dayjs(end) : undefined;
const isActive = medicationAdministration.status != "stopped";
const isWithinLastYear1 = medicationStart && medicationStart.isAfter(oneYearAgo);
const isWithinLastYear2 = medicationEnd && medicationEnd.isAfter(oneYearAgo);
const isWithinLastYear = isWithinLastYear1 || isWithinLastYear2;
return isActive && isWithinLastYear;
});

const medicationSection = createDischargeMedicationSection(
mappedMedications,
removeDuplicate,
"Condition Linked Medications"
activeMedicationsWithinLastYear,
"Active Medications"
);

const medicalTableContents = `
${medicationSection}
`;

return createSection("Medications", medicalTableContents);
return createSection("Discharge Medications", medicalTableContents);
}

export function createMedicationSection(
Expand All @@ -801,6 +821,7 @@ export function createMedicationSection(
}

const mappedMedications = mapResourceToId<Medication>(medications);
//console.log("mappedMedications", mappedMedications);

const medicationsSortedByDate = medicationStatements.sort((a, b) => {
return dayjs(a.effectivePeriod?.start).isBefore(dayjs(b.effectivePeriod?.start)) ? 1 : -1;
Expand All @@ -813,52 +834,53 @@ export function createMedicationSection(
return aDate === bDate && a.dosage?.[0]?.text === b.dosage?.[0]?.text;
});

const otherMedications = removeDuplicate.filter(
medicationStatement => medicationStatement.status !== ("active" || "unknown")
);

const activeMedications = removeDuplicate.filter(
medicationStatement => medicationStatement.status === "active"
);

const emptyMedications = removeDuplicate.filter(
medicationStatement => !medicationStatement.status || medicationStatement.status === "unknown"
);
const oneYearAgo = dayjs().subtract(1, "year");

const activeMedicationsWithinLastYear = activeMedications.filter(medicationStatement => {
const start = medicationStatement.effectivePeriod?.start;
const end = medicationStatement.effectivePeriod?.end;
const medicationStart = start ? dayjs(start) : undefined;
const medicationEnd = end ? dayjs(end) : undefined;
const isActive = medicationStatement.status === "active";
const isWithinLastYear1 = medicationStart && medicationStart.isAfter(oneYearAgo);
const isWithinLastYear2 = medicationEnd && medicationEnd.isAfter(oneYearAgo);
const isWithinLastYear = isWithinLastYear1 || isWithinLastYear2;
return isActive && isWithinLastYear;
});

// console.log("medications", medications);
// console.log("activeMedicationsWithinLastYear", activeMedicationsWithinLastYear);

const activeMedicationsSection = createSectionInMedications(
mappedMedications,
activeMedications,
activeMedicationsWithinLastYear,
"Active Medications"
);

const emptyMedicationsSection = createSectionInMedications(
mappedMedications,
emptyMedications,
"Unknown Status Medications"
);

const completedMedicationsSection = createSectionInMedications(
mappedMedications,
otherMedications,
"Other Status Medications"
);

const medicalTableContents = `
${activeMedicationsSection}
${emptyMedicationsSection}
${completedMedicationsSection}
`;

return createSection("Medications", medicalTableContents);
}

function getDateFromMedicationStatement(v: MedicationStatement): string | undefined {
function getStartDateFromMedicationStatement(v: MedicationStatement): string | undefined {
return v.effectivePeriod?.start;
}
function getEndDateFromMedicationStatement(v: MedicationStatement): string | undefined {
return v.effectivePeriod?.end;
}

function getDateFromMedicationAdministration(v: MedicationAdministration): string | undefined {
function getStartDateFromMedicationAdministration(v: MedicationAdministration): string | undefined {
return v.effectivePeriod?.start;
}
function getEndDateFromMedicationAdministration(v: MedicationAdministration): string | undefined {
return v.effectivePeriod?.end;
}

function createSectionInMedications(
mappedMedications: Record<string, Medication>,
Expand All @@ -870,8 +892,8 @@ function createSectionInMedications(
return ` <h4>${title}</h4><table><tbody><tr><td>${noMedFound}</td></tr></tbody></table>`;
}
const medicationStatementsSortedByDate = medicationStatements.sort((a, b) => {
const aDate = getDateFromMedicationStatement(a);
const bDate = getDateFromMedicationStatement(b);
const aDate = getStartDateFromMedicationStatement(a);
const bDate = getStartDateFromMedicationStatement(b);
if (!aDate && !bDate) return 0;
if (aDate && !bDate) return -1;
if (!aDate && bDate) return 1;
Expand All @@ -888,7 +910,8 @@ function createSectionInMedications(
<th>Dosage</th>
<th>Status</th>
<th>Code</th>
<th>Date</th>
<th> Start Date</th>
<th> End Date</th>
</div>
</tr>
</thead>
Expand Down Expand Up @@ -923,7 +946,10 @@ function createSectionInMedications(
<td>${medicationStatement.status ?? ""}</td>
<td>${code ?? ""}</td>
<td>${formatDateForDisplay(
getDateFromMedicationStatement(medicationStatement)
getStartDateFromMedicationStatement(medicationStatement)
)}</td>
<td>${formatDateForDisplay(
getEndDateFromMedicationStatement(medicationStatement)
)}</td>
</tr>
`;
Expand All @@ -935,7 +961,7 @@ function createSectionInMedications(
return medicalTableContents;
}

function createMedLinkedConditionSectionInMedications(
function createDischargeMedicationSection(
mappedMedications: Record<string, Medication>,
medicationAdministrations: MedicationAdministration[],
title: string
Expand All @@ -945,8 +971,8 @@ function createMedLinkedConditionSectionInMedications(
return ` <h4>${title}</h4><table><tbody><tr><td>${noMedFound}</td></tr></tbody></table>`;
}
const medicationStatementsSortedByDate = medicationAdministrations.sort((a, b) => {
const aDate = getDateFromMedicationAdministration(a);
const bDate = getDateFromMedicationAdministration(b);
const aDate = getStartDateFromMedicationAdministration(a);
const bDate = getStartDateFromMedicationAdministration(b);
if (!aDate && !bDate) return 0;
if (aDate && !bDate) return -1;
if (!aDate && bDate) return 1;
Expand All @@ -963,7 +989,8 @@ function createMedLinkedConditionSectionInMedications(
<th>Dosage</th>
<th>Status</th>
<th>Code</th>
<th>Date</th>
<th> Start Date</th>
<th> End Date</th>
</div>
</tr>
</thead>
Expand Down Expand Up @@ -992,7 +1019,12 @@ function createMedLinkedConditionSectionInMedications(
}</td>
<td>${medicationAdministration.status ?? ""}</td>
<td>${code ?? ""}</td>
<td>${formatDateForDisplay(medicationAdministration.effectivePeriod?.start)}</td>
<td>${formatDateForDisplay(
getStartDateFromMedicationAdministration(medicationAdministration)
)}</td>
<td>${formatDateForDisplay(
getEndDateFromMedicationAdministration(medicationAdministration)
)}</td>
</tr>
`;
})
Expand Down
46 changes: 20 additions & 26 deletions packages/fhir-converter/src/templates/cda/Sections/Medication.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,44 @@
{{#if medEntry.substanceAdministration.consumable.manufacturedProduct.manufacturedMaterial}}
{{>Resources/Medication.hbs medication=medEntry.substanceAdministration.consumable.manufacturedProduct.manufacturedMaterial ID=(generateUUID (toJsonString medEntry.substanceAdministration.consumable.manufacturedProduct.manufacturedMaterial))}},
{{>References/MedicationStatement/medicationReference.hbs ID=(generateUUID (toJsonString medEntry.substanceAdministration)) REF=(concat 'Medication/' (generateUUID (toJsonString medEntry.substanceAdministration.consumable.manufacturedProduct.manufacturedMaterial)))}},
{{#each (toArray medEntry.substanceAdministration.entryRelationship) as |medReq|}}
{{#if medReq.supply}}
{{>Resources/MedicationRequest.hbs medicationRequest=medReq.supply ID=(generateUUID (toJsonString medReq.supply))}},
{{#each (toArray medEntry.substanceAdministration.entryRelationship) as |entryRelationship|}}
{{#if entryRelationship.supply}}
{{>Resources/MedicationRequest.hbs medicationRequest=entryRelationship.supply ID=(generateUUID (toJsonString entryRelationship.supply))}},
{{#with (evaluate 'Utils/GeneratePatientId.hbs' obj=@metriportPatientId) as |patientId|}}
{{>References/MedicationRequest/subject.hbs ID=(generateUUID (toJsonString medReq.supply)) REF=(concat 'Patient/' patientId.Id)}},
{{>References/MedicationRequest/subject.hbs ID=(generateUUID (toJsonString entryRelationship.supply)) REF=(concat 'Patient/' patientId.Id)}},
{{/with}}
{{>References/MedicationRequest/medicationReference.hbs ID=(generateUUID (toJsonString medReq.supply)) REF=(concat 'Medication/' (generateUUID (toJsonString medEntry.substanceAdministration.consumable.manufacturedProduct.manufacturedMaterial)))}},
{{>References/MedicationRequest/medicationReference.hbs ID=(generateUUID (toJsonString entryRelationship.supply)) REF=(concat 'Medication/' (generateUUID (toJsonString medEntry.substanceAdministration.consumable.manufacturedProduct.manufacturedMaterial)))}},
{{/if}}
{{!-- I am keeping what was originally here but I have yet to see an author placed here --}}
{{#if medReq.supply.author.assignedAuthor}}
{{#with (evaluate 'Utils/GeneratePractitionerId.hbs' obj=medReq.supply.author.assignedAuthor) as |practitionerId|}}
{{>Resources/Practitioner.hbs practitioner=medReq.supply.author.assignedAuthor ID=practitionerId.Id}},
{{>References/MedicationRequest/requester.hbs ID=(generateUUID (toJsonString medReq.supply)) REF=(concat 'Practitioner/' practitionerId.Id)}}
{{#if entryRelationship.supply.author.assignedAuthor}}
{{#with (evaluate 'Utils/GeneratePractitionerId.hbs' obj=entryRelationship.supply.author.assignedAuthor) as |practitionerId|}}
{{>Resources/Practitioner.hbs practitioner=entryRelationship.supply.author.assignedAuthor ID=practitionerId.Id}},
{{>References/MedicationRequest/requester.hbs ID=(generateUUID (toJsonString entryRelationship.supply)) REF=(concat 'Practitioner/' practitionerId.Id)}}
{{/with}}
{{#if medReq.supply.author.assignedAuthor.representedOrganization}}
{{#with (evaluate 'Utils/GenerateOrganizationId.hbs' obj=medReq.supply.author.assignedAuthor.representedOrganization) as |orgId|}}
{{>Resources/Organization.hbs org=medReq.supply.author.assignedAuthor.representedOrganization ID=orgId.Id}},
{{>References/MedicationRequest/requester.hbs ID=(generateUUID (toJsonString medReq.supply)) REF=(concat 'Organization/' orgId.Id)}}
{{#if entryRelationship.supply.author.assignedAuthor.representedOrganization}}
{{#with (evaluate 'Utils/GenerateOrganizationId.hbs' obj=entryRelationship.supply.author.assignedAuthor.representedOrganization) as |orgId|}}
{{>Resources/Organization.hbs org=entryRelationship.supply.author.assignedAuthor.representedOrganization ID=orgId.Id}},
{{>References/MedicationRequest/requester.hbs ID=(generateUUID (toJsonString entryRelationship.supply)) REF=(concat 'Organization/' orgId.Id)}}
{{/with}}
{{/if}}
{{else if (and medReq.supply medEntry.substanceAdministration.author.assignedAuthor)}}
{{else if (and entryRelationship.supply medEntry.substanceAdministration.author.assignedAuthor)}}
{{#with (evaluate 'Utils/GeneratePractitionerId.hbs' obj=medEntry.substanceAdministration.author.assignedAuthor) as |practitionerId|}}
{{>Resources/Practitioner.hbs practitioner=medEntry.substanceAdministration.author.assignedAuthor ID=practitionerId.Id}},
{{>References/MedicationRequest/requester.hbs ID=(generateUUID (toJsonString medReq.supply)) REF=(concat 'Practitioner/' practitionerId.Id)}}
{{>References/MedicationRequest/requester.hbs ID=(generateUUID (toJsonString entryRelationship.supply)) REF=(concat 'Practitioner/' practitionerId.Id)}}
{{/with}}
{{#if medEntry.substanceAdministration.author.assignedAuthor.representedOrganization}}
{{#with (evaluate 'Utils/GenerateOrganizationId.hbs' obj=medEntry.substanceAdministration.author.assignedAuthor.representedOrganization) as |orgId|}}
{{>Resources/Organization.hbs org=medEntry.substanceAdministration.author.assignedAuthor.representedOrganization ID=orgId.Id}},
{{>References/MedicationRequest/requester.hbs ID=(generateUUID (toJsonString medReq.supply)) REF=(concat 'Organization/' orgId.Id)}}
{{>References/MedicationRequest/requester.hbs ID=(generateUUID (toJsonString entryRelationship.supply)) REF=(concat 'Organization/' orgId.Id)}}
{{/with}}
{{/if}}
{{else if (contains (toJsonString entryRelationship.observation.templateId) '2.16.840.1.113883.10.20.22.4.19')}}
Copy link
Member

Choose a reason for hiding this comment

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

I can't tell what the OID is without looking up online. This is a yellow flag, as it increases dependency on the internet and external services. Maybe we can start moving those shared constants on a single file?

{{>Resources/Condition.hbs conditionEntry=entryRelationship.observation ID=(generateUUID (toJsonString entryRelationship.observation))}},
{{!-- add condition recorder here --}}
{{>References/MedicationStatement/reasonReference.hbs ID=(generateUUID (toJsonString medEntry.substanceAdministration)) REF=(concat 'Condition/' (generateUUID (toJsonString entryRelationship.observation)))}},
{{/if}}

{{/each}}
{{/if}}
{{#if medEntry.substanceAdministration.precondition}}
{{#each (toArray medEntry.substanceAdministration.precondition) as |precondition|}}
{{#if precondition.criterion.value.code}}
{{>Resources/Condition.hbs conditionEntry=precondition.criterion ID=(generateUUID (toJsonString criterion.value))}},
{{>References/MedicationStatement/reasonReference.hbs ID=(generateUUID (toJsonString medEntry.substanceAdministration)) REF=(concat 'Condition/' (generateUUID (toJsonString criterion.value)))}},
{{/if}}
{{/each}}
{{/if}}

{{#if medEntry.substanceAdministration.informant.assignedEntity.representedOrganization.name._}}
{{>Resources/Organization.hbs org=medEntry.substanceAdministration.informant.assignedEntity.representedOrganization ID=(generateUUID (toJsonString medEntry.substanceAdministration.informant.assignedEntity.representedOrganization))}},
{{/if}}
Expand Down
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the main customer facing output of this PR. A script that for a given customer, gets all their patients consolidated data and performs the filtering and MR summary generation

Copy link
Member Author

Choose a reason for hiding this comment

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

Full script for running this whole process

Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function ensureDirectory(): Promise<void> {

async function fetchAndSavePatientData(patientId: string): Promise<void> {
try {
const resources = ["Conditions, Procedures, MedicationAdministration"];
const resources = ["Condition, Procedure, MedicationStatement, MedicationAdministration"];
const data = await metriportApi.getPatientConsolidated(patientId, resources);
const fhirPatient = await getFhirPatientData(patientId);
const resourceWrappedFhirPatient = { resource: fhirPatient };
Expand Down
4 changes: 2 additions & 2 deletions packages/utils/src/fhir-converter/e2e-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ dayjs.extend(duration);
* - fhirBaseUrl: the URL of the FHIR server;
*/

const cdaLocation = ``;
const converterBaseUrl = "http:https://localhost:8777";
const cdaLocation = `/Users/jonahkaye/Desktop/2024-01-23T08:02:29.892Z/test-patient-sample`;
Copy link
Member

Choose a reason for hiding this comment

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

left-over

const converterBaseUrl = "http:https://localhost:8080";
const fhirBaseUrl = "http:https://localhost:8889";
const parallelConversions = 10;
// Execute 1 batch at a time to avoid concurrency when upserting resources (resulting in 409/Conflict), which
Expand Down
Loading
Loading