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

IHE gateway v2: adding support for telecoms, ssns, multiple names and addresses + adding types #2143

Merged
merged 15 commits into from
May 30, 2024
Merged
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { Patient as FHIRPatient } from "@medplum/fhirtypes";
import { OutboundPatientDiscoveryReq, XCPDGateway } from "@metriport/ihe-gateway-sdk";
import {
OutboundPatientDiscoveryReq,
XCPDGateway,
PatientResource,
} from "@metriport/ihe-gateway-sdk";
import dayjs from "dayjs";
import { HieInitiator } from "../hie/get-hie-initiator";
import { createPurposeOfUse, getSystemUserName } from "./shared";

export function createOutboundPatientDiscoveryReq({
patient,
patientResource,
cxId,
patientId,
xcpdGateways,
initiator,
requestId,
}: {
patient: FHIRPatient;
patientResource: PatientResource;
cxId: string;
patientId: string;
xcpdGateways: XCPDGateway[];
Expand Down Expand Up @@ -41,6 +44,6 @@ export function createOutboundPatientDiscoveryReq({
homeCommunityId: initiator.oid,
purposeOfUse: createPurposeOfUse(),
},
patientResource: patient,
patientResource,
};
}
8 changes: 4 additions & 4 deletions packages/api/src/external/carequality/patient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Patient, PatientExternalData } from "@metriport/core/domain/patient";
import { toFHIR } from "@metriport/core/external/fhir/patient/index";
import { toIheGatewayPatientResource } from "@metriport/core/external/fhir/patient/index";
import { MedicalDataSource } from "@metriport/core/external/index";
import { processAsyncError } from "@metriport/core/util/error/shared";
import { out } from "@metriport/core/util/log";
Expand Down Expand Up @@ -113,15 +113,15 @@ async function prepareForPatientDiscovery(
pdRequestGatewayV1: OutboundPatientDiscoveryReq;
pdRequestGatewayV2: OutboundPatientDiscoveryReq;
}> {
const fhirPatient = toFHIR(patient);
const patientResource = toIheGatewayPatientResource(patient);

const [{ v1Gateways, v2Gateways }, initiator] = await Promise.all([
gatherXCPDGateways(patient),
getCqInitiator(patient, facilityId),
]);

const pdRequestGatewayV1 = createOutboundPatientDiscoveryReq({
patient: fhirPatient,
patientResource,
cxId: patient.cxId,
patientId: patient.id,
xcpdGateways: v1Gateways,
Expand All @@ -130,7 +130,7 @@ async function prepareForPatientDiscovery(
});

const pdRequestGatewayV2 = createOutboundPatientDiscoveryReq({
patient: fhirPatient,
patientResource,
cxId: patient.cxId,
patientId: patient.id,
xcpdGateways: v2Gateways,
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.

reverting redox specific logic

Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@ const redoxGatewayOid = "2.16.840.1.113883.3.6147.458.2";
*/
const gatewaysThatAcceptOneDocRefPerRequest = [pointClickCareOid, redoxOid, redoxGatewayOid];

/*
* These gateways require that the home community ID in the DR request is the same as the one
* in the gateway. But these gateways also return different home community IDs in the DQ response
* than in the gateway. So we need to handle this and use the request home community ID instead of the response.
*/
const enforceSameHomeCommunityIdList = [pointClickCareOid, redoxOid, redoxGatewayOid];

function requiresMetriportOidInsteadOfInitiatorOid(gateway: XCPDGateway | XCAGateway): boolean {
return requiresMetriportOidUrl.includes(gateway.url);
}
Expand All @@ -51,7 +44,3 @@ export function requiresUrnInSoapBody(gateway: XCPDGateway): boolean {
export function requiresOnlyOneDocRefPerRequest(gateway: XCAGateway): boolean {
return gatewaysThatAcceptOneDocRefPerRequest.includes(gateway.homeCommunityId);
}

export function requiresRequestHomeCommunityId(gateway: XCAGateway): boolean {
return enforceSameHomeCommunityIdList.includes(gateway.homeCommunityId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,42 @@ export const outboundXcpdRequest: OutboundPatientDiscoveryReq = {
],
};

export const outboundXcpdRequestMissingFields: OutboundPatientDiscoveryReq = {
id: uuidv4(),
cxId: uuidv4(),
patientId: uuidv4(),
timestamp: "2024-04-04T19:11:55.879Z",
principalCareProviderIds: ["1234567890"],
samlAttributes: {
subjectId: "America Inc",
subjectRole: {
code: "106331006",
display: "Administrative AND/OR managerial worker",
},
organization: "White House Medical Inc",
organizationId: "2.16.840.1.113883.3.9621.5.213",
homeCommunityId: "2.16.840.1.113883.3.9621.5.213",
purposeOfUse: "TREATMENT",
},
patientResource: {
name: [
{
given: ["NWHINONE"],
family: "NWHINZZZTESTPATIENT",
},
],
gender: "male",
birthDate: "19810101",
},
gateways: [
{
url: "https://mock-metriport/soap/iti55",
oid: "2.16.840.1.113883.3.787.0.0",
id: "018ea97e-7b1c-78e9-8aa1-47c7caf85afe",
},
],
};

export const expectedXcpdResponse: OutboundPatientDiscoveryRespSuccessfulSchema = {
id: outboundXcpdRequest.id,
patientId: outboundXcpdRequest.patientId,
Expand Down Expand Up @@ -91,8 +127,9 @@ export const expectedMultiNameAddressResponse: OutboundPatientDiscoveryRespSucce
...expectedXcpdResponse,
patientResource: {
...expectedXcpdResponse.patientResource,
birthDate: expectedXcpdResponse.patientResource.birthDate,
name: [
...(expectedXcpdResponse.patientResource?.name ?? []),
...expectedXcpdResponse.patientResource.name,
{
given: ["nwhinone", "bartholomew"],
family: "nwhinzzztestpatient",
Expand All @@ -108,6 +145,22 @@ export const expectedMultiNameAddressResponse: OutboundPatientDiscoveryRespSucce
country: "USA",
},
],
telecom: [
{
system: "MC",
value: "tel:+1310-000-0000",
},
{
system: "H",
value: "mailto:test@test.com",
},
],
identifier: [
{
value: "987564321",
system: "2.16.840.1.113883.3.9621",
},
],
},
};

Expand Down Expand Up @@ -317,3 +370,16 @@ export const testFilesForUploadVerification = [
{ name: "test.txt", mimeType: "text/plain", fileExtension: ".txt" },
{ name: "test.jpeg", mimeType: "image/jpeg", fileExtension: ".jpeg" },
];

export const TEST_CERT = `-----BEGIN CERTIFICATE-----
MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW
MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEy
MzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPd
Vu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9x
O3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8juf
z2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEU
MBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcN
AQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5
sT/txBnVJGziyO8DPYdu2fPMER8ajJfl
-----END CERTIFICATE-----`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { outboundXcpdRequestMissingFields, TEST_CERT } from "./constants";
import { createITI5SoapEnvelope } from "../xcpd/create/iti55-envelope";

describe("createITI5SoapEnvelope", () => {
it("should process the match XCPD response correctly", async () => {
const response = createITI5SoapEnvelope({
bodyData: outboundXcpdRequestMissingFields,
publicCert: TEST_CERT,
});
console.log(JSON.stringify(response, null, 2));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
<postalCode>35080</postalCode>
<country>USA</country>
</addr>
<asOtherIDs classCode="PAT">
<id/>
</asOtherIDs>
<telecom/>
jonahkaye marked this conversation as resolved.
Show resolved Hide resolved
</patientPerson>
<subjectOf1>
<queryMatchObservation classCode="OBS" moodCode="EVN">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@
</addr>
<telecom use="MC" value="tel:+1310-000-0000" />
<telecom use="H" value="mailto:test@test.com" />
<asOtherIDs classCode="PAT">
<id root="2.16.840.1.113883.3.9621"
extension="987564321" />
<scopingOrganization classCode="ORG"
determinerCode="INSTANCE">
<id root="2.16.840.1.113883.3.9621" />
</scopingOrganization>
</asOtherIDs>
</patientPerson>
<subjectOf1>
<queryMatchObservation classCode="OBS" moodCode="EVN">
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.

Redox endpoints actually do want the home community id of the response and not the request. I misunderstood this before, possibly because PCC works with both, while the other 2 redox endpoints only work with the response home community id.

This was clarified over slack with redox technical support

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 redox specific logic

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
} from "../../../../shared";
import { successStatus, partialSuccessStatus } from "./constants";
import { capture } from "../../../../../../util/notifications";
import { requiresRequestHomeCommunityId } from "../../../gateways";

type Identifier = {
_identificationScheme: string;
Expand All @@ -40,10 +39,6 @@ type Slot = {
};
};

function getRequestHomeCommunityId(request: OutboundDocumentQueryReq): string {
return request.gateway.homeCommunityId;
}

function getResponseHomeCommunityId(
//eslint-disable-next-line @typescript-eslint/no-explicit-any
extrinsicObject: any
Expand All @@ -56,9 +51,6 @@ function getHomeCommunityIdForDr(
//eslint-disable-next-line @typescript-eslint/no-explicit-any
extrinsicObject: any
): string {
if (requiresRequestHomeCommunityId(request.gateway)) {
return getRequestHomeCommunityId(request);
}
return getResponseHomeCommunityId(extrinsicObject);
}

Expand Down
Loading
Loading