-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refs: #1667 Signed-off-by: Jonah Kaye <[email protected]>
- Loading branch information
Showing
41 changed files
with
881 additions
and
562 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 7 additions & 16 deletions
23
packages/api/src/command/medical/patient/convert-fhir-to-cda.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,30 @@ | ||
import { out } from "@metriport/core/util/log"; | ||
import { capture } from "@metriport/core/util/notifications"; | ||
import { makeFhirToCdaConverter } from "../../../external/fhir-to-cda-converter/converter-factory"; | ||
import { toFHIR as toFHIROrganization } from "../../../external/fhir/organization"; | ||
import { Bundle } from "../../../routes/medical/schemas/fhir"; | ||
import { getOrganizationOrFail } from "../organization/get-organization"; | ||
|
||
export async function convertFhirToCda({ | ||
cxId, | ||
patientId, | ||
docId, | ||
validatedBundle, | ||
splitCompositions = true, | ||
}: { | ||
cxId: string; | ||
patientId: string; | ||
docId: string; | ||
validatedBundle: Bundle; | ||
}): Promise<void> { | ||
const { log } = out(`convertFhirToCda - cxId: ${cxId}, patientId: ${patientId}`); | ||
splitCompositions?: boolean; | ||
}): Promise<string[]> { | ||
const { log } = out(`convertFhirToCda - cxId: ${cxId}`); | ||
const cdaConverter = makeFhirToCdaConverter(); | ||
const organization = await getOrganizationOrFail({ cxId }); | ||
|
||
try { | ||
const fhirOrganization = toFHIROrganization(organization); | ||
await cdaConverter.requestConvert({ | ||
return cdaConverter.requestConvert({ | ||
cxId, | ||
patientId, | ||
docId, | ||
bundle: validatedBundle, | ||
organization: fhirOrganization, | ||
orgOid: organization.oid, | ||
splitCompositions, | ||
}); | ||
} catch (error) { | ||
const msg = `Error converting FHIR to CDA`; | ||
log(`${msg} - error: ${error}`); | ||
capture.error(msg, { extra: { error, cxId, patientId } }); | ||
capture.error(msg, { extra: { error, cxId, splitCompositions } }); | ||
throw error; | ||
} | ||
} |
110 changes: 110 additions & 0 deletions
110
packages/api/src/command/medical/patient/handle-data-contributions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { createUploadFilePath } from "@metriport/core/domain/document/upload"; | ||
import { Patient } from "@metriport/core/domain/patient"; | ||
import { toFHIR as toFhirPatient } from "@metriport/core/external/fhir/patient/index"; | ||
import { uploadCdaDocuments, uploadFhirBundleToS3 } from "@metriport/core/fhir-to-cda/upload"; | ||
import { uuidv7 } from "@metriport/core/util/uuid-v7"; | ||
import BadRequestError from "../../../errors/bad-request"; | ||
import { processCcdRequest } from "../../../external/cda/process-ccd-request"; | ||
import { toFHIR as toFhirOrganization } from "../../../external/fhir/organization"; | ||
import { countResources } from "../../../external/fhir/patient/count-resources"; | ||
import { hydrateBundle } from "../../../external/fhir/shared/hydrate-bundle"; | ||
import { validateFhirEntries } from "../../../external/fhir/shared/json-validator"; | ||
import { Bundle as ValidBundle } from "../../../routes/medical/schemas/fhir"; | ||
import { Config } from "../../../shared/config"; | ||
import { getOrganizationOrFail } from "../organization/get-organization"; | ||
import { createOrUpdateConsolidatedPatientData } from "./consolidated-create"; | ||
import { convertFhirToCda } from "./convert-fhir-to-cda"; | ||
import { getPatientOrFail } from "./get-patient"; | ||
|
||
const MAX_RESOURCE_COUNT_PER_REQUEST = 50; | ||
const MAX_RESOURCE_STORED_LIMIT = 1000; | ||
|
||
export async function handleDataContribution({ | ||
patientId, | ||
cxId, | ||
bundle, | ||
}: { | ||
patientId: string; | ||
cxId: string; | ||
bundle: ValidBundle; | ||
}) { | ||
const [organization, patient] = await Promise.all([ | ||
getOrganizationOrFail({ cxId }), | ||
getPatientOrFail({ id: patientId, cxId }), | ||
]); | ||
|
||
const fhirOrganization = toFhirOrganization(organization); | ||
const fhirPatient = toFhirPatient(patient); | ||
const docId = uuidv7(); | ||
const fhirBundleDestinationKey = createUploadFilePath( | ||
cxId, | ||
patientId, | ||
`${docId}_FHIR_BUNDLE.json` | ||
); | ||
const fullBundle = hydrateBundle(bundle, fhirPatient, fhirOrganization, fhirBundleDestinationKey); | ||
const validatedBundle = validateFhirEntries(fullBundle); | ||
const incomingAmount = validatedBundle.entry.length; | ||
|
||
await checkResourceLimit(incomingAmount, patient); | ||
await uploadFhirBundleToS3({ | ||
cxId, | ||
patientId, | ||
fhirBundle: validatedBundle, | ||
destinationKey: fhirBundleDestinationKey, | ||
}); | ||
const consolidatedDataUploadResults = await createOrUpdateConsolidatedPatientData({ | ||
cxId, | ||
patientId: patient.id, | ||
fhirBundle: validatedBundle, | ||
}); | ||
|
||
const convertAndUploadCdaPromise = async () => { | ||
const isValidForCdaConversion = hasCompositionResource(validatedBundle); | ||
if (isValidForCdaConversion) { | ||
const converted = await convertFhirToCda({ | ||
cxId, | ||
validatedBundle, | ||
}); | ||
await uploadCdaDocuments({ | ||
cxId, | ||
patientId, | ||
cdaBundles: converted, | ||
organization: fhirOrganization, | ||
docId, | ||
}); | ||
} | ||
}; | ||
const createAndUploadCcdPromise = async () => { | ||
// TODO: To minimize generating CCDs, make it a delayed job (run it ~5min after it was initiated, only once for all requests within that time window) | ||
await processCcdRequest(patient, fhirOrganization); | ||
}; | ||
|
||
await Promise.all([createAndUploadCcdPromise(), convertAndUploadCdaPromise()]); | ||
return consolidatedDataUploadResults; | ||
} | ||
|
||
async function checkResourceLimit(incomingAmount: number, patient: Patient) { | ||
if (!Config.isCloudEnv() || Config.isSandbox()) { | ||
const { total: currentAmount } = await countResources({ | ||
patient: { id: patient.id, cxId: patient.cxId }, | ||
}); | ||
if (currentAmount + incomingAmount > MAX_RESOURCE_STORED_LIMIT) { | ||
throw new BadRequestError( | ||
`Reached maximum number of resources per patient in Sandbox mode.`, | ||
null, | ||
{ currentAmount, incomingAmount, MAX_RESOURCE_STORED_LIMIT } | ||
); | ||
} | ||
// Limit the amount of resources that can be created at once | ||
if (incomingAmount > MAX_RESOURCE_COUNT_PER_REQUEST) { | ||
throw new BadRequestError(`Cannot create this many resources at a time.`, null, { | ||
incomingAmount, | ||
MAX_RESOURCE_COUNT_PER_REQUEST, | ||
}); | ||
} | ||
} | ||
} | ||
|
||
function hasCompositionResource(bundle: ValidBundle): boolean { | ||
return bundle.entry.some(entry => entry.resource?.resourceType === "Composition"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.