diff --git a/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.spec.ts b/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.spec.ts index 19fd2142..5b0134fe 100644 --- a/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.spec.ts +++ b/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.spec.ts @@ -9,6 +9,8 @@ import { v4 as uuidv4 } from 'uuid'; // @ts-ignore import * as FHIR401Client_ProcessBundle from './fixtures/FHIR401Client_ProcessBundle.json'; +// @ts-ignore +import * as FHIR401Client_ExtractResourceReference from './fixtures/FHIR401Client_ExtractResourceReference.json'; import {IDatabaseRepository} from '../../../database/interface'; import {PouchdbCrypto} from '../../../database/plugins/crypto'; import {ClientConfig} from '../../../models/client/client-config'; @@ -101,4 +103,17 @@ describe('FHIR401Client', () => { }, 10000); }) + describe('ExtractResourceReference', () => { + it('should correctly extract resource identifier from raw resources', async () => { + + //setup + + //test + const resp = await client.ExtractResourceReference("CarePlan", FHIR401Client_ExtractResourceReference) + //expect + expect(resp.length).toEqual(2); + expect(resp).toEqual(['Encounter/97961321', 'Practitioner/12763770']); + }); + }) + }) diff --git a/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.ts b/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.ts index 9b0d1883..25e00cd3 100644 --- a/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.ts +++ b/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.ts @@ -1,4 +1,4 @@ -import {IClient, IResourceBundleRaw, IResourceRaw} from '../../interface'; +import {IClient, IResourceBundleEntryRaw, IResourceBundleRaw, IResourceRaw} from '../../interface'; import {BaseClient} from './base_client'; import {Source} from '../../../models/database/source'; import {IDatabaseRepository} from '../../../database/interface'; @@ -88,8 +88,29 @@ export class FHIR401Client extends BaseClient implements IClient { let bundle = await this.GetResourceBundlePaginated(`${resourceType}?patient=${this.source.patient}`) let wrappedResourceModels = await this.ProcessBundle(bundle) let resourceUpsertSummary = await db.UpsertResources(wrappedResourceModels) + upsertSummary.updatedResources = upsertSummary.updatedResources.concat(resourceUpsertSummary.updatedResources) upsertSummary.totalResources += resourceUpsertSummary.totalResources + + + // check if theres any "extracted" resource references that we should sync as well + let extractedResourceReferences = [] + extractedResourceReferences = wrappedResourceModels.reduce((previousVal, wrappedResource) => { + return previousVal.concat(this.ExtractResourceReference(wrappedResource.source_resource_type, wrappedResource.resource_raw)) + }, extractedResourceReferences) + + + if(extractedResourceReferences.length > 0 ){ + console.log("Extracted Resource References", extractedResourceReferences) + + let extractedResourceBundle = await this.GenerateResourceBundleFromResourceIds(extractedResourceReferences) + let wrappedExtractedResourceBundle = await this.ProcessBundle(extractedResourceBundle) + let extractedResourceUpsertSummary = await db.UpsertResources(wrappedExtractedResourceBundle) + + upsertSummary.updatedResources = upsertSummary.updatedResources.concat(extractedResourceUpsertSummary.updatedResources) + upsertSummary.totalResources += extractedResourceUpsertSummary.totalResources + } + } catch (e) { console.error(`An error occurred while processing ${resourceType} bundle ${this.source.patient}`) @@ -109,7 +130,7 @@ export class FHIR401Client extends BaseClient implements IClient { * @param resourceRaw * @constructor */ - public async ExtractResourceReference(sourceResourceType: string, resourceRaw): Promise { + public ExtractResourceReference(sourceResourceType: string, resourceRaw): string[] { let resourceRefs = [] switch (sourceResourceType) { @@ -258,16 +279,15 @@ export class FHIR401Client extends BaseClient implements IClient { resourceRefs.push(reasonReference.reference) }) - //hospitalization[x].origin can contain + //hospitalization.origin can contain //- Location //- Organization - //hospitalization[x].destination can contain + resourceRefs.push(resourceRaw.hospitalization?.origin?.reference) + + //hospitalization.destination can contain //- Location //- Organization - resourceRaw.hospitalization?.map((hospitalization) => { - resourceRefs.push(hospitalization.origin?.reference) - resourceRefs.push(hospitalization.destination?.reference) - }) + resourceRefs.push(resourceRaw.hospitalization?.destination?.reference) //location[x].location can contain //- Location @@ -440,7 +460,6 @@ export class FHIR401Client extends BaseClient implements IClient { // remove all null values, remove all duplicates let cleanResourceRefs = resourceRefs.filter(i => !(typeof i === 'undefined' || i === null)); cleanResourceRefs = [...new Set(cleanResourceRefs)] - return cleanResourceRefs } @@ -504,6 +523,24 @@ export class FHIR401Client extends BaseClient implements IClient { }) } + /** + * Given a list of resource ids (Patient/xxx, Observation/yyyy), request these resources and generate a pseudo-bundle file + * @param resourceIds + * @constructor + * @protected + */ + protected async GenerateResourceBundleFromResourceIds(resourceIds: string[]): Promise{ + + resourceIds = [...new Set(resourceIds)] //make sure they are unique references. + let rawResourceRefs = await Promise.all(resourceIds.map(async (extractedResourceRef) => { + return { + resource: await this.GetRequest(extractedResourceRef) as IResourceRaw + } as IResourceBundleEntryRaw + })) + + return {resourceType: "Bundle", entry: rawResourceRefs} + } + /** * Retrieve a resource bundle. While "next" link is present in response, continue to request urls and append BundleEntries * @param relativeResourcePath diff --git a/frontend/src/lib/conduit/fhir/base/fixtures/FHIR401Client_ExtractResourceReference.json b/frontend/src/lib/conduit/fhir/base/fixtures/FHIR401Client_ExtractResourceReference.json new file mode 100644 index 00000000..cd36120b --- /dev/null +++ b/frontend/src/lib/conduit/fhir/base/fixtures/FHIR401Client_ExtractResourceReference.json @@ -0,0 +1,41 @@ +{ + "resourceType": "CarePlan", + "id": "197543976", + "meta": { + "versionId": "1", + "lastUpdated": "2021-10-04T14:46:33.000Z" + }, + "text": { + "status": "additional", + "div": "

Care Plan

Patient: SMART, NANCYU NHA EEEEEE

Title: Comorbidities found via Retrieve Dx

Description: Malnutrition:Severe --- Inputs [Height: 165.1, Weight: 25, BMI (Calculated): 9.171615976368047]\n\nAnoxic Brain Injury\n\n

Status: Active

Intent: Plan

Category: Assessment and Plan of Treatment

Author: P., MD, Cardio

Effective Period: Oct 4, 2021 2:46 P.M. UTC

" + }, + "status": "active", + "intent": "plan", + "category": [ + { + "coding": [ + { + "system": "http://hl7.org/fhir/us/core/CodeSystem/careplan-category", + "code": "assess-plan", + "display": "Assessment and Plan of Treatment" + } + ], + "text": "Assessment and Plan of Treatment" + } + ], + "title": "Comorbidities found via Retrieve Dx", + "subject": { + "reference": "Patient/12724066", + "display": "SMART, NANCYU NHA EEEEEE" + }, + "encounter": { + "reference": "Encounter/97961321" + }, + "period": { + "start": "2021-10-04T14:46:33.000Z" + }, + "author": { + "reference": "Practitioner/12763770", + "display": "P., MD, Cardio" + } +} diff --git a/frontend/src/lib/conduit/fhir/cerner_client.ts b/frontend/src/lib/conduit/fhir/cerner_client.ts index dbe883e4..39b50089 100644 --- a/frontend/src/lib/conduit/fhir/cerner_client.ts +++ b/frontend/src/lib/conduit/fhir/cerner_client.ts @@ -18,23 +18,15 @@ export class CernerClient extends FHIR401Client implements IClient { * @constructor */ async SyncAll(db: IDatabaseRepository): Promise { - const supportedResources: string[] = [ - "AllergyIntolerance", - "CarePlan", - "CareTeam", - "Condition", + const supportedResources: string[] = this.usCoreResources.concat([ + "Account", + "Appointment", "Consent", - "Device", - "Encounter", "FamilyMemberHistory", - "Goal", - "Immunization", "InsurancePlan", "MedicationRequest", "NutritionOrder", - "Observation", "Person", - "Procedure", "Provenance", "Questionnaire", "QuestionnaireResponse", @@ -42,7 +34,7 @@ export class CernerClient extends FHIR401Client implements IClient { "Schedule", "ServiceRequest", "Slot", - ] + ]) return this.SyncAllByResourceName(db, supportedResources) }