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

1181 dont load all patients to trigger doc query #1019

Merged
merged 4 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/api-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metriport/api-sdk",
"version": "7.1.2",
"version": "7.1.3-alpha.0",
"description": "Metriport helps you access and manage health and medical data, through a single open source API.",
"author": "Metriport Inc. <[email protected]>",
"homepage": "https://metriport.com/",
Expand Down
6 changes: 4 additions & 2 deletions packages/api-sdk/src/medical/client/metriport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,12 @@ export class MetriportMedicalApi {
* Start a document query for the given patient across HIEs.
*
* @param patientId Patient ID for which to retrieve document metadata.
* @param facilityId The facility providing the NPI to support this operation.
* @param facilityId The facility providing the NPI to support this operation (optional).
* If not provided and the patient has only one facility, that one will be used.
* If not provided and the patient has multiple facilities, an error will be thrown.
* @return The document query request ID, progress & status indicating whether its being executed or not.
*/
async startDocumentQuery(patientId: string, facilityId: string): Promise<DocumentQuery> {
async startDocumentQuery(patientId: string, facilityId?: string): Promise<DocumentQuery> {
const resp = await this.api.post(`${DOCUMENT_URL}/query`, null, {
params: {
patientId,
Expand Down
14 changes: 12 additions & 2 deletions packages/api/src/command/medical/document/document-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,28 @@ export async function queryDocumentsAcrossHIEs({
}: {
cxId: string;
patientId: string;
facilityId: string;
facilityId?: string;
override?: boolean;
}): Promise<DocumentQueryProgress> {
const { log } = Util.out(`queryDocumentsAcrossHIEs - M patient ${patientId}`);

const patient = await getPatientOrFail({ id: patientId, cxId });
if (!isPatientAssociatedWithFacility(patient, facilityId)) {
if (facilityId && !isPatientAssociatedWithFacility(patient, facilityId)) {
throw new BadRequestError(`Patient not associated with given facility`, undefined, {
patientId: patient.id,
facilityId,
});
}
if (!facilityId && patient.facilityIds.length > 1) {
throw new BadRequestError(
`Patient is associated with more than one facility (facilityId is required)`,
undefined,
{
patientId: patient.id,
facilityIdCount: patient.facilityIds.length,
}
);
}
const docQueryProgress = patient.data.documentQueryProgress;
const requestId = getOrGenerateRequestId(docQueryProgress);

Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/errors/bad-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default class BadRequestError extends MetriportError {
constructor(
message = "Unexpected issue with the request - check inputs and try again",
cause?: unknown,
additionalInfo?: Record<string, string | undefined | null>
additionalInfo?: Record<string, string | number | undefined | null>
) {
super(message, cause, additionalInfo);
this.status = numericStatus;
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/errors/metriport-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default class MetriportError extends Error {
constructor(
message: string,
cause?: unknown,
readonly additionalInfo?: Record<string, string | undefined | null>
readonly additionalInfo?: Record<string, string | number | undefined | null>
) {
super(message);
this.cause = cause;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import { getAllPages } from "../../fhir/shared/paginated";
import { makeSearchServiceIngest } from "../../opensearch/file-search-connector-factory";
import { makeCommonWellAPI } from "../api";
import { groupCWErrors } from "../error-categories";
import { getPatientData, PatientDataCommonwell } from "../patient-shared";
import { getPatientDataWithSingleFacility, PatientDataCommonwell } from "../patient-shared";
import { sandboxGetDocRefsAndUpsert } from "./document-query-sandbox";
import {
CWDocumentWithMetriportData,
Expand All @@ -72,7 +72,7 @@ const lambdaClient = makeLambdaClient();
* This is likely to be a long-running function, so it should likely be called asynchronously.
*
* @param patient - the patient to query for
* @param facilityId - the facility to query for
* @param facilityId - the facility to query for (optional if the patient only has one facility)
* @param forceDownload - whether to force download the documents from CW, even if they are already
* on S3 (optional) - see `downloadDocsAndUpsertFHIR()` for the default value
* @param ignoreDocRefOnFHIRServer - whether to ignore the doc refs on the FHIR server and re-query
Expand All @@ -87,17 +87,16 @@ export async function queryAndProcessDocuments({
requestId,
}: {
patient: Patient;
facilityId: string;
facilityId?: string | undefined;
forceDownload?: boolean;
ignoreDocRefOnFHIRServer?: boolean;
ignoreFhirConversionAndUpsert?: boolean;
requestId: string;
}): Promise<number> {
const { log } = Util.out(`CW queryDocuments: ${requestId} - M patient ${patient.id}`);

const { organization, facility } = await getPatientData(patient, facilityId);

try {
const { organization, facility } = await getPatientDataWithSingleFacility(patient, facilityId);

if (Config.isSandbox()) {
const documentsSandbox = await sandboxGetDocRefsAndUpsert({
organization,
Expand Down Expand Up @@ -135,7 +134,7 @@ export async function queryAndProcessDocuments({
} catch (error) {
console.log(`Error: ${errorToString(error)}`);
processPatientDocumentRequest(
organization.cxId,
patient.cxId,
patient.id,
"medical.document-download",
MAPIWebhookStatus.failed
Expand Down Expand Up @@ -400,7 +399,7 @@ export async function downloadDocsAndUpsertFHIR({
requestId,
}: {
patient: Patient;
facilityId: string;
facilityId?: string;
documents: Document[];
forceDownload?: boolean;
ignoreDocRefOnFHIRServer?: boolean;
Expand Down Expand Up @@ -493,7 +492,7 @@ export async function downloadDocsAndUpsertFHIR({

if (!fileInfo.fileExists) {
uploadToS3 = async () => {
const { organization, facility } = await getPatientData(
const { organization, facility } = await getPatientDataWithSingleFacility(
{ id: patient.id, cxId },
facilityId
);
Expand Down
69 changes: 58 additions & 11 deletions packages/api/src/external/commonwell/patient-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
RequestMetadata,
StrongId,
} from "@metriport/commonwell-sdk";
import { MetriportError } from "@metriport/core/util/error/metriport-error";
import _, { minBy } from "lodash";
import { getPatientWithDependencies } from "../../command/medical/patient/get-patient";
import { Facility } from "../../domain/medical/facility";
Expand Down Expand Up @@ -174,24 +175,70 @@ function idsToAlertMessage(cwPatientId: string, personIds: string[]): string {
return `Patient CW ID: ${cwPatientId}; Person IDs: ${personIds.join(", ")}`;
}

export async function getPatientData(
patient: {
id: string;
cxId: string;
},
facilityId: string
): Promise<{
export type PatientDataWithOneFacility = {
organization: Organization;
facility: Facility;
}> {
type: "single";
};
export type PatientDataWithMultipleFacilities = {
organization: Organization;
facilities: Facility[];
type: "multiple";
};
export async function getPatientData(
patient: Pick<Patient, "id" | "cxId">
): Promise<PatientDataWithMultipleFacilities>;
export async function getPatientData(
patient: Pick<Patient, "id" | "cxId">,
facilityId: string
): Promise<PatientDataWithOneFacility>;
export async function getPatientData(
patient: Pick<Patient, "id" | "cxId">,
facilityId?: string
): Promise<PatientDataWithOneFacility | PatientDataWithMultipleFacilities> {
const { organization, facilities } = await getPatientWithDependencies(patient);
const facility = facilities.find(f => f.id === facilityId);
if (!facility) {
throw new BadRequestError(`Patient not associated with given facility`, undefined, {
if (facilityId) {
const facility = facilities.find(f => f.id === facilityId);
if (!facility) {
throw new BadRequestError(`Patient not associated with given facility`, undefined, {
patientId: patient.id,
facilityId,
});
}
return { organization, facility, type: "single" };
}

return { organization, facilities, type: "multiple" };
}

export async function getPatientDataWithSingleFacility(
patient: Pick<Patient, "id" | "cxId">,
facilityId?: string
): Promise<Omit<PatientDataWithOneFacility, "type">> {
const data = facilityId
? await getPatientData(patient, facilityId)
: await getPatientData(patient);
const organization = data.organization;
if (data.type === "single") {
return { organization, facility: data.facility };
}
const facility = data.facilities[0];
if (!data.facilities || !facility) {
throw new MetriportError(`Could not determine facility for patient`, undefined, {
patientId: patient.id,
facilityId,
});
}
if (data.facilities.length > 1) {
throw new MetriportError(
`Patient has more than one facility, facilityId is required`,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a nice quality of life change 💅

undefined,
{
patientId: patient.id,
facilities: data.facilities.length,
}
);
}
return { organization, facility };
}

Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/routes/medical/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ router.post(
asyncHandler(async (req: Request, res: Response) => {
const cxId = getCxIdOrFail(req);
const patientId = getFromQueryOrFail("patientId", req);
const facilityId = getFromQueryOrFail("facilityId", req);
const facilityId = getFrom("query").optional("facilityId", req);
const override = stringToBoolean(getFrom("query").optional("override", req));

const docQueryProgress = await queryDocumentsAcrossHIEs({
Expand Down
21 changes: 20 additions & 1 deletion packages/api/src/routes/medical/internal-patient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Router from "express-promise-router";
import status from "http-status";
import stringify from "json-stringify-safe";
import { z } from "zod";
import { getFacilities } from "../../command/medical/facility/get-facility";
import { getFacilities, getFacilityOrFail } from "../../command/medical/facility/get-facility";
import { deletePatient } from "../../command/medical/patient/delete-patient";
import { getPatientIds, getPatients } from "../../command/medical/patient/get-patient";
import { PatientUpdateCmd, updatePatient } from "../../command/medical/patient/update-patient";
Expand Down Expand Up @@ -37,6 +37,25 @@ async function updateInFHIRAndCW(
.catch(processAsyncError(`cw.patient.update`));
}

/** ---------------------------------------------------------------------------
* GET /internal/patient/ids
*
* Get all patient IDs for a given customer.
*
* @param req.query.cxId The customer ID.
* @returns 200 with the list of ids on the body under `patientIds`.
*/
router.get(
"/ids",
asyncHandler(async (req: Request, res: Response) => {
const cxId = getUUIDFrom("query", req, "cxId").orFail();
const facilityId = getFrom("query").optional("facilityId", req);
if (facilityId) await getFacilityOrFail({ cxId, id: facilityId });
const patientIds = await getPatientIds({ cxId, facilityId });
return res.status(status.OK).json({ patientIds });
})
);

const updateAllSchema = z.object({
patientIds: z.string().array().optional(),
});
Expand Down
16 changes: 3 additions & 13 deletions packages/api/src/shared/util.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Sample } from "@metriport/api-sdk/devices/models/common/sample";
import { debug as coreDebug, log as coreLog } from "@metriport/core/util/log";
import convert from "convert-units";
import crypto from "crypto";
import { mean } from "lodash";
import { Stream } from "stream";
import { debug } from "./log";

// Useful as catch handler for asynchonous promises so we don't get an unhandled promise rejection
// eslint-disable-next-line @typescript-eslint/no-empty-function
Expand Down Expand Up @@ -79,21 +79,11 @@ export class Util {
/**
* @deprecated Use @metriport/core instead
*/
static log =
(prefix: string, suffix?: string) =>
//eslint-disable-next-line @typescript-eslint/no-explicit-any
(msg: string, ...optionalParams: any[]): void =>
optionalParams
? console.log(`[${prefix}] ${msg}`, ...[...optionalParams, ...([suffix] ?? [])])
: console.log(`[${prefix}] ${msg} - ${suffix}`);
static log = coreLog;
/**
* @deprecated Use @metriport/core instead
*/
static debug =
(prefix: string, suffix?: string) =>
//eslint-disable-next-line @typescript-eslint/no-explicit-any
(msg: string, ...optionalParams: any[]): void =>
debug(`[${prefix}] ${msg}`, ...[...optionalParams, ...([suffix] ?? [])]);
static debug = coreDebug;
/**
* @deprecated Use @metriport/core instead
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/connect-widget/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "connect-widget",
"version": "1.4.6",
"version": "1.4.7-alpha.0",
"private": true,
"scripts": {
"clean": "rimraf build && rimraf node_modules",
Expand Down Expand Up @@ -38,7 +38,7 @@
"@chakra-ui/react": "^2.4.1",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@metriport/api-sdk": "^7.1.2",
"@metriport/api-sdk": "^7.1.3-alpha.0",
"@sentry/react": "^7.45.0",
"@sentry/tracing": "^7.45.0",
"@testing-library/jest-dom": "^5.16.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/util/error/metriport-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export class MetriportError extends Error {
constructor(
message: string,
readonly cause?: unknown,
readonly additionalInfo?: Record<string, string | undefined | null>
readonly additionalInfo?: Record<string, string | number | undefined | null>
) {
super(message);
this.cause = cause;
Expand Down
8 changes: 4 additions & 4 deletions packages/lambdas/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/lambdas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@aws-sdk/client-secrets-manager": "^3.348.0",
"@medplum/core": "^2.0.32",
"@medplum/fhirtypes": "^2.0.32",
"@metriport/api-sdk": "^7.1.2",
"@metriport/api-sdk": "^7.1.3-alpha.0",
"@metriport/commonwell-sdk": "^4.8.0",
"@metriport/core": "^1.2.2",
"@opensearch-project/opensearch": "^2.3.1",
Expand Down
Loading
Loading