Skip to content

Commit e4719ef

Browse files
authored
Merge pull request #79 from mcode/base-fhir-extractor-context
Updated Context Management in BaseFHIRExtractor
2 parents 816f9f5 + 94df556 commit e4719ef

13 files changed

+72
-175
lines changed

src/client/BaseClient.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class BaseClient {
3939
});
4040
}
4141

42+
// NOTE: Async because in other clients that extend this, we need async helper functions (ex. auth)
4243
async init() {
4344
return this.initializeExtractors(this.extractorConfig, this.commonExtractorArgs);
4445
}

src/extractors/BaseFHIRExtractor.js

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
const { Extractor } = require('./Extractor');
22
const { BaseFHIRModule } = require('../modules');
3-
const { determineVersion, mapFHIRVersions, isBundleEmpty, getBundleResourcesByType } = require('../helpers/fhirUtils');
3+
const { determineVersion, mapFHIRVersions, isBundleEmpty } = require('../helpers/fhirUtils');
4+
const { getPatientFromContext } = require('../helpers/contextUtils');
45
const logger = require('../helpers/logger');
56

6-
function parseContextForPatientId(context) {
7-
const patientInContext = getBundleResourcesByType(context, 'Patient', {}, true);
8-
return patientInContext ? patientInContext.id : undefined;
9-
}
10-
117
class BaseFHIRExtractor extends Extractor {
128
constructor({ baseFhirUrl, requestHeaders, version, resourceType }) {
139
super();
@@ -20,19 +16,14 @@ class BaseFHIRExtractor extends Extractor {
2016
this.baseFHIRModule.updateRequestHeaders(newHeaders);
2117
}
2218

23-
// Use mrn to get PatientId by default; common need across almost all extractors
19+
/* eslint-disable class-methods-use-this */
20+
// Use context to get PatientId by default; common need across almost all extractors
21+
// NOTE: Async because other extractors that extend this may need to make async lookups in the future
2422
async parametrizeArgsForFHIRModule({ mrn, context }) {
25-
const idFromContext = parseContextForPatientId(context);
26-
if (idFromContext) return { patient: idFromContext };
27-
28-
logger.debug('No patient ID in context; fetching with baseFHIRModule');
29-
const patientResponseBundle = await this.baseFHIRModule.search('Patient', { identifier: `MRN|${mrn}` });
30-
if (!patientResponseBundle || !patientResponseBundle.entry || !patientResponseBundle.entry[0] || !patientResponseBundle.entry[0].resource) {
31-
logger.error(`Could not get a patient ID to cross-reference for ${this.resourceType}`);
32-
return {};
33-
}
34-
return { patient: patientResponseBundle.entry[0].resource.id };
23+
const patient = getPatientFromContext(mrn, context);
24+
return { patient: patient.id };
3525
}
26+
/* eslint-enable class-methods-use-this */
3627

3728
// Since different superclasses of the baseFHIRExtractor will parse the `get`
3829
// arguments differently, all pass to this function which interfaces with the baseFHIRModule

src/extractors/MCODERadiationProcedureExtractor.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function getMCODERadiationProcedures(fhirProcedures) {
1515
class MCODERadiationProcedureExtractor extends Extractor {
1616
constructor({ baseFhirUrl, requestHeaders }) {
1717
super({ baseFhirUrl, requestHeaders });
18+
logger.debug('Note that MCODERadiationProcedureExtractor uses FHIR to get FHIR Procedures.');
1819
this.fhirProcedureExtractor = new FHIRProcedureExtractor({ baseFhirUrl, requestHeaders });
1920
}
2021

src/extractors/MCODESurgicalProcedureExtractor.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function getMCODESurgicalProcedures(fhirProcedures) {
1515
class MCODESurgicalProcedureExtractor extends Extractor {
1616
constructor({ baseFhirUrl, requestHeaders }) {
1717
super({ baseFhirUrl, requestHeaders });
18+
logger.debug('Note that MCODESurgicalProcedureExtractor uses FHIR to get FHIR Procedures.');
1819
this.fhirProcedureExtractor = new FHIRProcedureExtractor({ baseFhirUrl, requestHeaders });
1920
}
2021

test/extractors/BaseFHIRExtractor.test.js

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
const { when } = require('jest-when');
2-
const rewire = require('rewire');
32
const { BaseFHIRExtractor } = require('../../src/extractors');
43
const examplePatientBundle = require('./fixtures/patient-bundle.json');
54
const exampleConditionBundle = require('./fixtures/condition-bundle.json');
65

7-
const BaseFHIRExtractorRewired = rewire('../../src/extractors/BaseFHIRExtractor.js');
8-
96
// Constants for mock tests
107
const MOCK_URL = 'http://localhost';
118
const MOCK_REQUEST_HEADERS = {
@@ -18,7 +15,7 @@ const MOCK_CONTEXT = {
1815
entry: [
1916
{
2017
fullUrl: 'context-url',
21-
resource: { resourceType: 'Patient', id: 'MOCK-ID' },
18+
resource: { resourceType: 'Patient', id: MOCK_PATIENT_MRN },
2219
},
2320
],
2421
};
@@ -31,9 +28,6 @@ const { baseFHIRModule } = baseFHIRExtractor;
3128
const baseFHIRModuleSearchSpy = jest.spyOn(baseFHIRModule, 'search');
3229
const moduleRequestHeadersSpy = jest.spyOn(baseFHIRModule, 'updateRequestHeaders');
3330

34-
when(baseFHIRModuleSearchSpy)
35-
.calledWith('Patient', { identifier: `MRN|${MOCK_PATIENT_MRN}` })
36-
.mockReturnValue(examplePatientBundle);
3731
// Ensure that data is returned for condition
3832
when(baseFHIRModuleSearchSpy)
3933
.calledWith('Condition', { patient: examplePatientBundle.entry[0].resource.id })
@@ -47,17 +41,6 @@ describe('BaseFhirExtractor', () => {
4741
expect(moduleRequestHeadersSpy).toHaveBeenCalledWith(MOCK_REQUEST_HEADERS);
4842
});
4943

50-
const parseContextForPatientId = BaseFHIRExtractorRewired.__get__('parseContextForPatientId');
51-
test('parseContextForPatientId returns undefined when no context is provided', () => {
52-
const emptyValue = parseContextForPatientId({});
53-
expect(emptyValue).toBeUndefined();
54-
});
55-
56-
test('parseContextForPatientId returns the patient ID when one exists on the contextBundle', () => {
57-
const idValue = parseContextForPatientId(MOCK_CONTEXT);
58-
expect(idValue).toEqual(MOCK_CONTEXT.entry[0].resource.id);
59-
});
60-
6144
test('parametrizeArgsForFHIRModule parses data off of context if available', async () => {
6245
baseFHIRModuleSearchSpy.mockClear();
6346
const paramsBasedOnContext = await baseFHIRExtractor.parametrizeArgsForFHIRModule({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT });
@@ -66,16 +49,14 @@ describe('BaseFhirExtractor', () => {
6649
expect(paramsBasedOnContext.patient).toEqual(MOCK_CONTEXT.entry[0].resource.id);
6750
});
6851

69-
test('parametrizeArgsForFHIRModule makes Patient call if context has no relevant ID', async () => {
52+
test('parametrizeArgsForFHIRModule throws an error if context has no relevant ID', async () => {
7053
baseFHIRModuleSearchSpy.mockClear();
71-
const paramsBasedOnContext = await baseFHIRExtractor.parametrizeArgsForFHIRModule({ mrn: MOCK_PATIENT_MRN });
72-
expect(baseFHIRModuleSearchSpy).toHaveBeenCalled();
73-
expect(paramsBasedOnContext).toHaveProperty('patient');
74-
expect(paramsBasedOnContext.patient).toEqual(examplePatientBundle.entry[0].resource.id);
54+
await expect(baseFHIRExtractor.parametrizeArgsForFHIRModule({ mrn: MOCK_PATIENT_MRN, context: {} })).rejects.toThrow();
55+
expect(baseFHIRModuleSearchSpy).not.toHaveBeenCalled();
7556
});
7657

7758
test('get should return a condition resource', async () => {
78-
const data = await baseFHIRExtractor.get({ mrn: MOCK_PATIENT_MRN });
59+
const data = await baseFHIRExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT });
7960
expect(data.resourceType).toEqual('Bundle');
8061
expect(data.entry).toBeDefined();
8162
expect(data.entry.length).toBeGreaterThan(0);

test/extractors/FHIRAdverseEventExtractor.test.js

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
const rewire = require('rewire');
22
const { FHIRAdverseEventExtractor } = require('../../src/extractors/FHIRAdverseEventExtractor');
3-
const examplePatientBundle = require('./fixtures/patient-bundle.json');
43

54
const FHIRAdverseEventExtractorRewired = rewire('../../src/extractors/FHIRAdverseEventExtractor.js');
65
const MOCK_URL = 'http://example.com';
76
const MOCK_HEADERS = {};
87
const MOCK_MRN = '123456789';
98
const MOCK_STUDIES = 'study1,study2';
10-
11-
// Construct extractor and create sppies for mocking responses
9+
const MOCK_CONTEXT = {
10+
resourceType: 'Bundle',
11+
entry: [
12+
{
13+
fullUrl: 'context-url',
14+
resource: { resourceType: 'Patient', id: MOCK_MRN },
15+
},
16+
],
17+
};
18+
19+
// Construct extractor and create spies for mocking responses
1220
const extractor = new FHIRAdverseEventExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS });
1321
const extractorWithStudy = new FHIRAdverseEventExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS, study: MOCK_STUDIES });
1422
const baseStudy = FHIRAdverseEventExtractorRewired.__get__('BASE_STUDY');
@@ -28,33 +36,19 @@ describe('FHIRAdverseEventExtractor', () => {
2836

2937
describe('parametrizeArgsForFHIRModule', () => {
3038
test('should not add study when not set to param values', async () => {
31-
// Create spy
32-
const { baseFHIRModule } = extractor;
33-
const baseFHIRModuleSearchSpy = jest.spyOn(baseFHIRModule, 'search');
34-
baseFHIRModuleSearchSpy
35-
.mockReturnValue(examplePatientBundle);
36-
37-
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN });
39+
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN, context: MOCK_CONTEXT });
3840
expect(params).not.toHaveProperty('study');
3941
});
4042

4143
describe('pass in optional study parameter', () => {
42-
beforeEach(() => {
43-
// Create spy
44-
const { baseFHIRModule } = extractorWithStudy;
45-
const baseFHIRModuleSearchSpy = jest.spyOn(baseFHIRModule, 'search');
46-
baseFHIRModuleSearchSpy
47-
.mockReturnValue(examplePatientBundle);
48-
});
49-
5044
test('should add study when set to param values', async () => {
51-
const params = await extractorWithStudy.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN });
45+
const params = await extractorWithStudy.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN, context: MOCK_CONTEXT });
5246
expect(params).toHaveProperty('study');
5347
expect(params.study).toEqual(extractorWithStudy.study);
5448
});
5549

5650
test('should delete patient after its value is moved to subject', async () => {
57-
const params = await extractorWithStudy.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN });
51+
const params = await extractorWithStudy.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN, context: MOCK_CONTEXT });
5852
expect(params).not.toHaveProperty('patient');
5953
});
6054
});

test/extractors/FHIRAllergyIntoleranceExtractor.test.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
const rewire = require('rewire');
22
const { FHIRAllergyIntoleranceExtractor } = require('../../src/extractors/FHIRAllergyIntoleranceExtractor.js');
3-
const examplePatientBundle = require('./fixtures/patient-bundle.json');
43

54
const FHIRAllergyIntoleranceExtractorRewired = rewire('../../src/extractors/FHIRAllergyIntoleranceExtractor.js');
65
const MOCK_URL = 'http://example.com';
76
const MOCK_HEADERS = {};
87
const MOCK_MRN = '123456789';
98
const MOCK_CLINICAL_STATUS = 'status1,status2';
9+
const MOCK_CONTEXT = {
10+
resourceType: 'Bundle',
11+
entry: [
12+
{
13+
fullUrl: 'context-url',
14+
resource: { resourceType: 'Patient', id: MOCK_MRN },
15+
},
16+
],
17+
};
1018

1119
const extractor = new FHIRAllergyIntoleranceExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS });
1220
const extractorWithClinicalStatus = new FHIRAllergyIntoleranceExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS, clinicalStatus: MOCK_CLINICAL_STATUS });
@@ -29,12 +37,7 @@ describe('FHIRAllergyIntoleranceExtractor', () => {
2937

3038
describe('parametrizeArgsForFHIRModule', () => {
3139
test('should add category to param values', async () => {
32-
// Create spy
33-
const { baseFHIRModule } = extractor;
34-
const baseFHIRModuleSearchSpy = jest.spyOn(baseFHIRModule, 'search');
35-
baseFHIRModuleSearchSpy.mockReturnValue(examplePatientBundle);
36-
37-
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN });
40+
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN, context: MOCK_CONTEXT });
3841
expect(params).toHaveProperty('clinical-status');
3942
expect(params['clinical-status']).toEqual(baseClinicalStatus);
4043
});

test/extractors/FHIRConditionExtractor.test.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
const rewire = require('rewire');
22
const { FHIRConditionExtractor } = require('../../src/extractors/FHIRConditionExtractor.js');
3-
const examplePatientBundle = require('./fixtures/patient-bundle.json');
43

54
const FHIRConditionExtractorRewired = rewire('../../src/extractors/FHIRConditionExtractor');
65
const MOCK_URL = 'http://example.com';
76
const MOCK_HEADERS = {};
87
const MOCK_MRN = '123456789';
98
const MOCK_CATEGORIES = 'category1,category2';
9+
const MOCK_CONTEXT = {
10+
resourceType: 'Bundle',
11+
entry: [
12+
{
13+
fullUrl: 'context-url',
14+
resource: { resourceType: 'Patient', id: MOCK_MRN },
15+
},
16+
],
17+
};
1018

1119
const extractor = new FHIRConditionExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS });
1220
const extractorWithCategories = new FHIRConditionExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS, category: MOCK_CATEGORIES });
@@ -29,12 +37,7 @@ describe('FHIRConditionExtractor', () => {
2937

3038
describe('parametrizeArgsForFHIRModule', () => {
3139
test('should add category to param values', async () => {
32-
// Create spy
33-
const { baseFHIRModule } = extractor;
34-
const baseFHIRModuleSpy = jest.spyOn(baseFHIRModule, 'search');
35-
baseFHIRModuleSpy.mockReturnValue(examplePatientBundle);
36-
37-
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN });
40+
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN, context: MOCK_CONTEXT });
3841
expect(params).toHaveProperty('category');
3942
expect(params.category).toEqual(baseCategories);
4043
});

test/extractors/FHIRMedicationRequestExtractor.test.js

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
const rewire = require('rewire');
22
const { FHIRMedicationRequestExtractor } = require('../../src/extractors/FHIRMedicationRequestExtractor.js');
3-
const examplePatientBundle = require('./fixtures/patient-bundle.json');
43

54
const FHIRMedicationRequestExtractorRewired = rewire('../../src/extractors/FHIRMedicationRequestExtractor.js');
65
const MOCK_URL = 'http://example.com';
76
const MOCK_HEADERS = {};
87
const MOCK_MRN = '123456789';
98
const MOCK_STATUSES = 'status1,status2';
9+
const MOCK_CONTEXT = {
10+
resourceType: 'Bundle',
11+
entry: [
12+
{
13+
fullUrl: 'context-url',
14+
resource: { resourceType: 'Patient', id: MOCK_MRN },
15+
},
16+
],
17+
};
1018

1119
// Construct extractor and create spies for mocking responses
1220
const extractor = new FHIRMedicationRequestExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS });
@@ -28,24 +36,12 @@ describe('FHIRMedicationRequestExtractor', () => {
2836

2937
describe('parametrizeArgsForFHIRModule', () => {
3038
test('should not add status when not set to param values', async () => {
31-
// Create spy
32-
const { baseFHIRModule } = extractor;
33-
const baseFHIRModuleSearchSpy = jest.spyOn(baseFHIRModule, 'search');
34-
baseFHIRModuleSearchSpy
35-
.mockReturnValue(examplePatientBundle);
36-
37-
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN });
39+
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN, context: MOCK_CONTEXT });
3840
expect(params).not.toHaveProperty('status');
3941
});
4042

4143
test('should add status when set to param values', async () => {
42-
// Create spy
43-
const { baseFHIRModule } = extractorWithStatuses;
44-
const baseFHIRModuleSearchSpy = jest.spyOn(baseFHIRModule, 'search');
45-
baseFHIRModuleSearchSpy
46-
.mockReturnValue(examplePatientBundle);
47-
48-
const params = await extractorWithStatuses.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN });
44+
const params = await extractorWithStatuses.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN, context: MOCK_CONTEXT });
4945
expect(params).toHaveProperty('status');
5046
expect(params.status).toEqual(extractorWithStatuses.status);
5147
});

test/extractors/FHIRObservationExtractor.test.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
const rewire = require('rewire');
22
const { FHIRObservationExtractor } = require('../../src/extractors/FHIRObservationExtractor.js');
3-
const examplePatientBundle = require('./fixtures/patient-bundle.json');
43

54
const FHIRObservationExtractorRewired = rewire('../../src/extractors/FHIRObservationExtractor.js');
65
const MOCK_URL = 'http://example.com';
76
const MOCK_HEADERS = {};
87
const MOCK_MRN = '123456789';
98
const MOCK_CATEGORIES = 'category1,category2';
9+
const MOCK_CONTEXT = {
10+
resourceType: 'Bundle',
11+
entry: [
12+
{
13+
fullUrl: 'context-url',
14+
resource: { resourceType: 'Patient', id: MOCK_MRN },
15+
},
16+
],
17+
};
1018

1119
// Construct extractor and create spies for mocking responses
1220
const extractor = new FHIRObservationExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS });
@@ -28,13 +36,7 @@ describe('FHIRObservationExtractor', () => {
2836

2937
describe('parametrizeArgsForFHIRModule', () => {
3038
test('should add category to param values', async () => {
31-
// Create spy
32-
const { baseFHIRModule } = extractor;
33-
const baseFHIRModuleSearchSpy = jest.spyOn(baseFHIRModule, 'search');
34-
baseFHIRModuleSearchSpy
35-
.mockReturnValue(examplePatientBundle);
36-
37-
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN });
39+
const params = await extractor.parametrizeArgsForFHIRModule({ mrn: MOCK_MRN, context: MOCK_CONTEXT });
3840
expect(params).toHaveProperty('category');
3941
expect(params.category).toEqual(baseCategories);
4042
});

0 commit comments

Comments
 (0)