-
Notifications
You must be signed in to change notification settings - Fork 67
198 ecw v2 #3874
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
198 ecw v2 #3874
Changes from all commits
26828b5
490e018
cc61012
86bc814
fed7dc2
f3e465e
f3316c5
778a89e
1d2efc7
ff0a390
485c193
a88c3b6
d75ac45
c586ffa
1e8250c
34845fa
3bab8e6
f0ba08c
b87a2a8
10b3932
24be3f5
3ddeeb2
4684373
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import EClinicalWorksApi from "@metriport/core/external/ehr/eclinicalworks/index"; | ||
import { processAsyncError } from "@metriport/core/util/error/shared"; | ||
import { MetriportError } from "@metriport/shared"; | ||
import { eclinicalworksDashSource } from "@metriport/shared/interface/external/ehr/eclinicalworks/jwt-token"; | ||
import { EhrSources } from "@metriport/shared/interface/external/ehr/source"; | ||
import { getJwtTokenByIdOrFail } from "../../../../command/jwt-token"; | ||
import { findOrCreatePatientMapping, getPatientMapping } from "../../../../command/mapping/patient"; | ||
import { queryDocumentsAcrossHIEs } from "../../../../command/medical/document/document-query"; | ||
import { getPatientOrFail } from "../../../../command/medical/patient/get-patient"; | ||
import { | ||
createMetriportPatientDemosFhir, | ||
getOrCreateMetriportPatientFhir, | ||
} from "../../shared/utils/fhir"; | ||
import { createEClinicalWorksClient } from "../shared"; | ||
|
||
export type SyncEClinicalWorksPatientIntoMetriportParams = { | ||
cxId: string; | ||
eclinicalworksPracticeId: string; | ||
eclinicalworksPatientId: string; | ||
eclinicalworksTokenId: string; | ||
api?: EClinicalWorksApi; | ||
triggerDq?: boolean; | ||
}; | ||
|
||
export async function syncEClinicalWorksPatientIntoMetriport({ | ||
cxId, | ||
eclinicalworksPracticeId, | ||
eclinicalworksPatientId, | ||
eclinicalworksTokenId, | ||
api, | ||
triggerDq = false, | ||
}: SyncEClinicalWorksPatientIntoMetriportParams): Promise<string> { | ||
const existingPatient = await getPatientMapping({ | ||
cxId, | ||
externalId: eclinicalworksPatientId, | ||
source: EhrSources.eclinicalworks, | ||
}); | ||
if (existingPatient) { | ||
const metriportPatient = await getPatientOrFail({ | ||
cxId, | ||
id: existingPatient.patientId, | ||
}); | ||
const metriportPatientId = metriportPatient.id; | ||
return metriportPatientId; | ||
} | ||
|
||
const eclinicalworksApi = | ||
api ?? | ||
(await createEClinicalWorksClientFromTokenId({ | ||
cxId, | ||
practiceId: eclinicalworksPracticeId, | ||
tokenId: eclinicalworksTokenId, | ||
})); | ||
const eclinicalworksPatient = await eclinicalworksApi.getPatient({ | ||
cxId, | ||
patientId: eclinicalworksPatientId, | ||
}); | ||
const possibleDemographics = createMetriportPatientDemosFhir(eclinicalworksPatient); | ||
const metriportPatient = await getOrCreateMetriportPatientFhir({ | ||
cxId, | ||
source: EhrSources.eclinicalworks, | ||
practiceId: eclinicalworksPracticeId, | ||
possibleDemographics, | ||
externalId: eclinicalworksPatientId, | ||
}); | ||
if (triggerDq) { | ||
queryDocumentsAcrossHIEs({ | ||
cxId, | ||
patientId: metriportPatient.id, | ||
}).catch(processAsyncError(`EClinicalWorks queryDocumentsAcrossHIEs`)); | ||
} | ||
await findOrCreatePatientMapping({ | ||
cxId, | ||
patientId: metriportPatient.id, | ||
externalId: eclinicalworksPatientId, | ||
source: EhrSources.eclinicalworks, | ||
}); | ||
return metriportPatient.id; | ||
} | ||
|
||
async function createEClinicalWorksClientFromTokenId({ | ||
cxId, | ||
practiceId, | ||
tokenId, | ||
}: { | ||
cxId: string; | ||
practiceId: string; | ||
tokenId: string; | ||
}): Promise<EClinicalWorksApi> { | ||
const token = await getJwtTokenByIdOrFail(tokenId); | ||
if (token.data.source !== eclinicalworksDashSource) { | ||
throw new MetriportError("Invalid token source", undefined, { | ||
tokenId, | ||
source: token.data.source, | ||
}); | ||
} | ||
const tokenPracticeId = token.data.practiceId; | ||
if (tokenPracticeId !== practiceId) { | ||
throw new MetriportError("Invalid token practiceId", undefined, { | ||
tokenId, | ||
source: token.data.source, | ||
tokenPracticeId, | ||
practiceId, | ||
}); | ||
} | ||
const api = await createEClinicalWorksClient({ | ||
cxId, | ||
practiceId, | ||
authToken: token.token, | ||
}); | ||
return api; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import EClinicalWorksApi, { | ||
EClinicalWorksEnv, | ||
isEClinicalWorksEnv, | ||
} from "@metriport/core/external/ehr/eclinicalworks/index"; | ||
import { MetriportError } from "@metriport/shared"; | ||
import { Config } from "../../../shared/config"; | ||
import { EhrPerPracticeParams } from "../shared/utils/client"; | ||
|
||
function getEClinicalWorksEnv(): { | ||
environment: EClinicalWorksEnv; | ||
} { | ||
const environment = Config.getEClinicalWorksEnv(); | ||
if (!environment) throw new MetriportError("EClinicalWorks environment not set"); | ||
if (!isEClinicalWorksEnv(environment)) { | ||
throw new MetriportError("Invalid EClinicalWorks environment", undefined, { environment }); | ||
} | ||
return { | ||
environment, | ||
}; | ||
} | ||
|
||
type EClinicalWorksPerPracticeParams = EhrPerPracticeParams & { | ||
authToken: string; | ||
}; | ||
|
||
export async function createEClinicalWorksClient( | ||
perPracticeParams: EClinicalWorksPerPracticeParams | ||
): Promise<EClinicalWorksApi> { | ||
const { environment } = getEClinicalWorksEnv(); | ||
return await EClinicalWorksApi.create({ | ||
practiceId: perPracticeParams.practiceId, | ||
environment, | ||
authToken: perPracticeParams.authToken, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { eclinicalworksDashSource } from "@metriport/shared/interface/external/ehr/eclinicalworks/jwt-token"; | ||
import { NextFunction, Request, Response } from "express"; | ||
import { JwtTokenData } from "../../../../domain/jwt-token"; | ||
import ForbiddenError from "../../../../errors/forbidden"; | ||
import { | ||
ParseResponse, | ||
processCxId as processCxIdShared, | ||
processDocumentRoute as processDocumentRouteShared, | ||
processPatientRoute as processPatientRouteShared, | ||
} from "../../shared"; | ||
|
||
export const tokenEhrPatientIdQueryParam = "eclinicalworksPatientIdFromToken"; | ||
|
||
function parseEClinicalWorksPracticeIdDash( | ||
tokenData: JwtTokenData, | ||
tokenId: string | ||
): ParseResponse { | ||
if (tokenData.source !== eclinicalworksDashSource) throw new ForbiddenError(); | ||
const practiceId = tokenData.practiceId; | ||
if (!practiceId) throw new ForbiddenError(); | ||
const patientId = tokenData.patientId; | ||
if (!patientId) throw new ForbiddenError(); | ||
return { | ||
externalId: practiceId, | ||
queryParams: { | ||
practiceId, | ||
tokenId, | ||
[tokenEhrPatientIdQueryParam]: patientId, | ||
}, | ||
}; | ||
} | ||
|
||
export function processCxIdDash(req: Request, res: Response, next: NextFunction) { | ||
processCxIdShared(req, eclinicalworksDashSource, parseEClinicalWorksPracticeIdDash) | ||
.then(next) | ||
.catch(next); | ||
} | ||
|
||
export function processPatientRoute(req: Request, res: Response, next: NextFunction) { | ||
processPatientRouteShared(req, eclinicalworksDashSource).then(next).catch(next); | ||
} | ||
|
||
export function processDocumentRoute(req: Request, res: Response, next: NextFunction) { | ||
processDocumentRouteShared(req, eclinicalworksDashSource).then(next).catch(next); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { Request, Response } from "express"; | ||
import Router from "express-promise-router"; | ||
import httpStatus from "http-status"; | ||
import { syncEClinicalWorksPatientIntoMetriport } from "../../../external/ehr/eclinicalworks/command/sync-patient"; | ||
import { handleParams } from "../../helpers/handle-params"; | ||
import { requestLogger } from "../../helpers/request-logger"; | ||
import { asyncHandler, getCxIdOrFail, getFrom, getFromQueryOrFail } from "../../util"; | ||
|
||
const router = Router(); | ||
|
||
/** | ||
* GET /ehr/eclinicalworks/patient/:id | ||
* | ||
* Tries to retrieve the matching Metriport patient | ||
* @param req.params.id The ID of Eclinicalworks Patient. | ||
* @param req.query.practiceId The ID of Eclinicalworks Practice. | ||
* @param req.query.tokenId The ID of Eclinicalworks Token. | ||
* @returns Metriport Patient if found. | ||
*/ | ||
router.get( | ||
"/:id", | ||
handleParams, | ||
requestLogger, | ||
asyncHandler(async (req: Request, res: Response) => { | ||
const cxId = getCxIdOrFail(req); | ||
const eclinicalworksPatientId = getFrom("params").orFail("id", req); | ||
const eclinicalworksPracticeId = getFromQueryOrFail("practiceId", req); | ||
const eclinicalworksTokenId = getFromQueryOrFail("tokenId", req); | ||
const patientId = await syncEClinicalWorksPatientIntoMetriport({ | ||
cxId, | ||
eclinicalworksPracticeId, | ||
eclinicalworksPatientId, | ||
eclinicalworksTokenId, | ||
}); | ||
return res.status(httpStatus.OK).json(patientId); | ||
}) | ||
); | ||
|
||
/** | ||
* POST /ehr/eclinicalworks/patient/:id | ||
* | ||
* Tries to retrieve the matching Metriport patient | ||
* @param req.params.id The ID of Eclinicalworks Patient. | ||
* @param req.query.practiceId The ID of Eclinicalworks Practice. | ||
* @param req.query.tokenId The ID of Eclinicalworks Token. | ||
* @returns Metriport Patient if found. | ||
*/ | ||
router.post( | ||
"/:id", | ||
handleParams, | ||
requestLogger, | ||
asyncHandler(async (req: Request, res: Response) => { | ||
const cxId = getCxIdOrFail(req); | ||
const eclinicalworksPatientId = getFrom("params").orFail("id", req); | ||
const eclinicalworksPracticeId = getFromQueryOrFail("practiceId", req); | ||
const eclinicalworksTokenId = getFromQueryOrFail("tokenId", req); | ||
const patientId = await syncEClinicalWorksPatientIntoMetriport({ | ||
cxId, | ||
eclinicalworksPracticeId, | ||
eclinicalworksPatientId, | ||
eclinicalworksTokenId, | ||
}); | ||
return res.status(httpStatus.OK).json(patientId); | ||
}) | ||
); | ||
|
||
Comment on lines
+39
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have the same logic but one is post and the other get? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We wanted to eventually migrate to POST as it creates a mapping on our end (so not a pure GET) but frontend needs an update to roll this out completely |
||
export default router; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we forget to do this before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes