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

Improvements to DQ and DR PRs #1528

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
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(cq): nits on dr pr
Refs: #1379
  • Loading branch information
jonahkaye committed Jan 28, 2024
commit ca03831f74ac318d9a6e0180c8a0f06e2ec5edd1
7 changes: 5 additions & 2 deletions packages/core/src/command/patient-loader-metriport-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,17 @@ export class PatientLoaderMetriportAPI implements PatientLoader {
lastNameInitial: data?.lastNameInitial,
},
});
// call convertToDomainObject(response) here
if (!response.data) {
Copy link
Member Author

Choose a reason for hiding this comment

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

response.data is of type any so checking if undefined to prevent runtime errors

console.log(`No patients found for ${JSON.stringify(data)}`);
throw new Error();
}
const patients: Patient[] = response.data.map((patient: PatientDTO) =>
getDomainFromDTO(patient)
);
patients.forEach(validatePatient);
return patients;
} catch (error) {
console.log("Failing on request to internal endpoint", error);
console.log(`Failing on request to internal endpoint ${JSON.stringify(error)}`);
Copy link
Member Author

Choose a reason for hiding this comment

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

no multiline errors

throw error;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ async function createAndUploadMetadataFile({
title,
});

console.log(`Uploading metadata to S3 with key: ${s3MetadataFileName}`);
console.log(
`Uploading metadata to S3 with key: ${s3MetadataFileName}, cxId: ${cxId}, patientId: ${patientId}`
Copy link
Member Author

Choose a reason for hiding this comment

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

more descriptive logging

);
await s3Utils.uploadFile(destinationBucket, s3MetadataFileName, Buffer.from(extrinsicObjectXml));
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export function createExtrinsicObjectXml({

const objectTypeClassification =
"urn:oasis:names:tc:ebxml-regrep:ObjectType:RegistryObject:Classification";
const externalIdentifierClassification =
"urn:oasis:names:tc:ebxml-regrep:ObjectType:RegistryObject:ExternalIdentifier";

const metadataXml = `<ExtrinsicObject home="${METRIPORT_HOME_COMMUNITY_ID}" id="${documentUUID}" isOpaque="false" mimeType="text/xml" objectType="${ON_DEMAND_OBJECT_TYPE}" status="urn:oasis:names:tc:ebxml-regrep:StatusType:Approved">

Expand Down Expand Up @@ -179,14 +181,14 @@ export function createExtrinsicObjectXml({
</Name>
</Classification>

<ExternalIdentifier id="${uuidv7()}" identificationScheme="${PATIENT_ID_CLASSIFICATION_SCHEME}" objectType="urn:oasis:names:tc:ebxml-regrep:ObjectType:RegistryObject:ExternalIdentifier" registryObject="${documentUUID}" value="${patientId}^^^&amp;${homeCommunityId}&amp;ISO">
<ExternalIdentifier id="${uuidv7()}" identificationScheme="${PATIENT_ID_CLASSIFICATION_SCHEME}" objectType=""${externalIdentifierClassification}"}" registryObject="${documentUUID}" value="${patientId}^^^&amp;${homeCommunityId}&amp;ISO">
<Name>
<LocalizedString charset="UTF-8" value="XDSDocumentEntry.patientId"/>
</Name>
</ExternalIdentifier>

<!-- (IHE) REQUIRED - DocumentEntry.uniqueId - Globally unique identifier assigned to the document by its creator -->
<ExternalIdentifier id="${uuidv7()}" identificationScheme="${DOCUMENT_ENTRY_CLASSIFICATION_SCHEME}" objectType="urn:oasis:names:tc:ebxml-regrep:ObjectType:RegistryObject:ExternalIdentifier" registryObject="${documentUUID}" value="${createDocumentUniqueId(
<ExternalIdentifier id="${uuidv7()}" identificationScheme="${DOCUMENT_ENTRY_CLASSIFICATION_SCHEME}" objectType=""${externalIdentifierClassification}"}" registryObject="${documentUUID}" value="${createDocumentUniqueId(
documentUniqueId
)}">
<Name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@ import {
DocumentQueryRespToExternalGW,
} from "@metriport/ihe-gateway-sdk";
import { validateDQ } from "./validating-dq";
import {
XDSUnknownPatientId,
XDSUnknownCommunity,
XDSMissingHomeCommunityId,
XDSRegistryError,
constructDQErrorResponse,
} from "../error";
import { IHEGatewayError, XDSRegistryError, constructDQErrorResponse } from "../error";

export async function processIncomingRequest(
payload: DocumentQueryReqFromExternalGW
Expand All @@ -28,12 +22,7 @@ export async function processIncomingRequest(

return response;
} catch (error) {
if (
error instanceof XDSUnknownPatientId ||
error instanceof XDSUnknownCommunity ||
error instanceof XDSMissingHomeCommunityId ||
error instanceof XDSRegistryError
) {
if (error instanceof IHEGatewayError) {
Copy link
Member Author

Choose a reason for hiding this comment

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

simplification since XDSUnknownPatientId etc are all instances of IHEGatewayError

return constructDQErrorResponse(payload, error);
} else {
return constructDQErrorResponse(payload, new XDSRegistryError());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import {
DocumentRetrievalReqFromExternalGW,
DocumentRetrievalRespToExternalGW,
} from "@metriport/ihe-gateway-sdk";
import { validateDR } from "./validating-dr";
import { validateDRAndRetrievePresignedUrls } from "./validating-dr";
import { IHEGatewayError, constructDRErrorResponse, XDSRegistryError } from "../error";

export async function processIncomingRequest(
payload: DocumentRetrievalReqFromExternalGW
): Promise<DocumentRetrievalRespToExternalGW> {
try {
// validate incoming request + look for patient and get all their documents from s3
const documents = await validateDR(payload);
const documents = await validateDRAndRetrievePresignedUrls(payload);
Copy link
Member Author

Choose a reason for hiding this comment

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

more descriptive function name


// construct response
const response: DocumentRetrievalRespToExternalGW = {
Expand Down
37 changes: 21 additions & 16 deletions packages/core/src/external/carequality/dr/validating-dr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { XDSRegistryError } from "../error";
const region = Config.getAWSRegion();
const medicalDocumentsBucketName = Config.getMedicalDocumentsBucketName();

export async function validateDR(
export async function validateDRAndRetrievePresignedUrls(
payload: DocumentRetrievalReqFromExternalGW
): Promise<DocumentReference[]> {
validateBasePayload(payload);
Expand All @@ -27,21 +27,26 @@ export async function validateDR(

async function retrievePreSignedUrls(documentIds: string[]): Promise<DocumentReference[]> {
const s3Utils = new S3Utils(region);
const documentReferences: DocumentReference[] = [];

for (const id of documentIds) {
const url = await s3Utils.getSignedUrl({
bucketName: medicalDocumentsBucketName,
fileName: id,
});
const documentReference: DocumentReference = {
homeCommunityId: METRIPORT_HOME_COMMUNITY_ID,
repositoryUniqueId: METRIPORT_REPOSITORY_UNIQUE_ID,
docUniqueId: id,
urn: url,
};
documentReferences.push(documentReference);
}

const promises = documentIds.map(id =>
Copy link
Member Author

Choose a reason for hiding this comment

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

batch for efficiency

s3Utils
.getSignedUrl({
bucketName: medicalDocumentsBucketName,
fileName: id,
})
.then(url => ({
homeCommunityId: METRIPORT_HOME_COMMUNITY_ID,
repositoryUniqueId: METRIPORT_REPOSITORY_UNIQUE_ID,
docUniqueId: id,
urn: url,
}))
);

const results = await Promise.allSettled(promises);

const documentReferences = results
.filter(result => result.status === "fulfilled")
.map(result => (result as PromiseFulfilledResult<DocumentReference>).value);

return documentReferences;
}
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/external/fhir/patient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { ContactTypes, Contact } from "../../../domain/contact";
import { Address } from "../../../domain/address";
import { GenderAtBirth, Patient, PersonalIdentifier, splitName } from "../../../domain/patient";
import { getIdFromSubjectId, getIdFromSubjectRef } from "../shared";
import { uuidv7 } from "../../../util/uuid-v7";

export const genderMapping: { [k in GenderAtBirth]: "female" | "male" } = {
F: "female",
Expand All @@ -20,7 +19,7 @@ export const genderMapping: { [k in GenderAtBirth]: "female" | "male" } = {
export const toFHIR = (patient: Pick<Patient, "id" | "data">): FHIRPatient => {
return {
resourceType: "Patient",
id: uuidv7(),
id: patient.id,
Copy link
Member Author

Choose a reason for hiding this comment

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

the culprit 😭

identifier: patient.data.personalIdentifiers
? convertDriversLicenseToIdentifier(patient.data.personalIdentifiers)
: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const documentQueryReqToExternalGWSchema = documentQueryDefaultReqSchema.
export type DocumentQueryReqToExternalGW = z.infer<typeof documentQueryReqToExternalGWSchema>;

// FROM EXTERNAL GATEWAY
export const documentQueryReqFromExternalGWSchema = documentQueryDefaultReqSchema;
export const documentQueryReqFromExternalGWSchema = documentQueryDefaultReqSchema.omit({
cxId: true,
Copy link
Member Author

Choose a reason for hiding this comment

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

omit cxId in this schema since incoming requests don't have cxId

});

export type DocumentQueryReqFromExternalGW = z.infer<typeof documentQueryReqFromExternalGWSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export type DocumentRetrievalReqToExternalGW = z.infer<
>;

// FROM EXTERNAL GATEWAY
export const documentRetrievalReqFromExternalGWSchema = documentRetrievalReqDefaultSchema;
export const documentRetrievalReqFromExternalGWSchema = documentRetrievalReqDefaultSchema.omit({
cxId: true,
});

export type DocumentRetrievalReqFromExternalGW = z.infer<
typeof documentRetrievalReqFromExternalGWSchema
Expand Down
2 changes: 1 addition & 1 deletion packages/ihe-gateway-sdk/src/models/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const SamlAttributesSchema = z.object({

export const baseRequestSchema = z.object({
id: z.string(),
cxId: z.string().optional(),
cxId: z.string(),
Copy link
Member Author

Choose a reason for hiding this comment

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

reverting this back to mandatory and modifying the incoming request types to omit cxId

timestamp: z.string(),
samlAttributes: SamlAttributesSchema,
patientId: z.string().optional(),
Expand Down
6 changes: 6 additions & 0 deletions packages/utils/src/carequality/mock-ihe-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { processIncomingRequest as processIncomingPdRequest } from "@metriport/c
import { MPIMetriportAPI } from "@metriport/core/mpi/patient-mpi-metriport-api";
import { getEnvVarOrFail } from "@metriport/core/util/env-var";

/*
This is a mock IHE gateway for Carequality interfaces to handle patient discovery (PD),
document query (DQ), and document retrieval (DR) requests. Use it for testing logic in
Copy link
Member Author

Choose a reason for hiding this comment

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

script explainer

@metriport/core
*/

import express, { Application, Request, Response } from "express";

const apiUrl = getEnvVarOrFail("API_URL");
Expand Down
2 changes: 1 addition & 1 deletion packages/utils/src/fhir-converter/shared.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Bundle, Resource, ResourceType } from "@medplum/fhirtypes";
import { parseS3FileName } from "@metriport/core/external/aws/s3";
import { parseS3FileName } from "@metriport/core/domain/utils";
import { getFileContentsAsync, getFileNames } from "../shared/fs";
import { uuidv7 } from "../shared/uuid-v7";

Expand Down
Loading