Skip to content

Commit 971897f

Browse files
committed
Require Patient resource to be in context for all FHIR resource extractors
- Remove BaseFHIRModule search for Patient if resource is not in bundle - If Patient is not in bundle, throw an error - Update BaseFHIRExtractor tests to test for thrown error and use context - Update all other FHIR extractors to use context and remove unused mock functions
1 parent 816f9f5 commit 971897f

10 files changed

+70
-151
lines changed

src/extractors/BaseFHIRExtractor.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,18 @@ class BaseFHIRExtractor extends Extractor {
2020
this.baseFHIRModule.updateRequestHeaders(newHeaders);
2121
}
2222

23-
// Use mrn to get PatientId by default; common need across almost all extractors
24-
async parametrizeArgsForFHIRModule({ mrn, context }) {
23+
/* eslint-disable class-methods-use-this */
24+
// Use context to get PatientId by default; common need across almost all extractors
25+
async parametrizeArgsForFHIRModule({ context }) {
2526
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 {};
27+
if (idFromContext) {
28+
logger.debug('Patient found in context');
29+
return { patient: idFromContext };
3330
}
34-
return { patient: patientResponseBundle.entry[0].resource.id };
31+
32+
throw new Error('BaseFHIRExtractor could not find Patient resource in context. Use an extractor to get a Patient resource first.');
3533
}
34+
/* eslint-enable class-methods-use-this */
3635

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

test/extractors/BaseFHIRExtractor.test.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const MOCK_CONTEXT = {
1818
entry: [
1919
{
2020
fullUrl: 'context-url',
21-
resource: { resourceType: 'Patient', id: 'MOCK-ID' },
21+
resource: { resourceType: 'Patient', id: MOCK_PATIENT_MRN },
2222
},
2323
],
2424
};
@@ -66,16 +66,14 @@ describe('BaseFhirExtractor', () => {
6666
expect(paramsBasedOnContext.patient).toEqual(MOCK_CONTEXT.entry[0].resource.id);
6767
});
6868

69-
test('parametrizeArgsForFHIRModule makes Patient call if context has no relevant ID', async () => {
69+
test('parametrizeArgsForFHIRModule throws an error if context has no relevant ID', async () => {
7070
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);
71+
await expect(baseFHIRExtractor.parametrizeArgsForFHIRModule({ mrn: MOCK_PATIENT_MRN })).rejects.toThrowError('BaseFHIRExtractor could not find Patient resource in context.');
72+
expect(baseFHIRModuleSearchSpy).not.toHaveBeenCalled();
7573
});
7674

7775
test('get should return a condition resource', async () => {
78-
const data = await baseFHIRExtractor.get({ mrn: MOCK_PATIENT_MRN });
76+
const data = await baseFHIRExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT });
7977
expect(data.resourceType).toEqual('Bundle');
8078
expect(data.entry).toBeDefined();
8179
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
});

test/extractors/MCODERadiationProcedureExtractor.test.js

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,44 +18,6 @@ const fhirProcedureExtractorSpy = jest.spyOn(fhirProcedureExtractor, 'get');
1818

1919
describe('MCODERadiationProcedureExtractor', () => {
2020
describe('getFHIRProcedures', () => {
21-
// it('should return procedure entries for patient from context', async () => {
22-
// const contextPatient = {
23-
// resourceType: 'Patient',
24-
// id: 'context-patient-id',
25-
// };
26-
// const contextProcedure1 = {
27-
// resourceType: 'Procedure',
28-
// id: 'context-procedure-1-id',
29-
// };
30-
// const contextProcedure2 = {
31-
// resourceType: 'Procedure',
32-
// id: 'context-procedure-2-id',
33-
// };
34-
// const context = {
35-
// resourceType: 'Bundle',
36-
// type: 'collection',
37-
// entry: [
38-
// {
39-
// fullUrl: 'context-patient-url',
40-
// resource: contextPatient,
41-
// },
42-
// {
43-
// fullUrl: 'context-procedure-1-url',
44-
// resource: contextProcedure1,
45-
// },
46-
// {
47-
// fullUrl: 'context-procedure-2-url',
48-
// resource: contextProcedure2,
49-
// },
50-
// ],
51-
// };
52-
// const procedures = await extractor.getFHIRProcedures(MOCK_PATIENT_MRN, context);
53-
// expect(fhirProcedureExtractorSpy).not.toHaveBeenCalled();
54-
// expect(procedures).toHaveLength(2);
55-
// expect(procedures[0].resource.id).toEqual(contextProcedure1.id);
56-
// expect(procedures[1].resource.id).toEqual(contextProcedure2.id);
57-
// });
58-
5921
it('should return procedure entries for patient from FHIR search if no context', async () => {
6022
fhirProcedureExtractorSpy.mockClear();
6123
when(fhirProcedureExtractorSpy).calledWith({ mrn: MOCK_PATIENT_MRN, context: {} }).mockReturnValue(exampleProcedureBundle);

test/extractors/MCODESurgicalProcedureExtractor.test.js

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,44 +18,6 @@ const fhirProcedureExtractorSpy = jest.spyOn(fhirProcedureExtractor, 'get');
1818

1919
describe('MCODESurgicalProcedureExtractor', () => {
2020
describe('getFHIRProcedures', () => {
21-
// it('should return procedure entries for patient from context', async () => {
22-
// const contextPatient = {
23-
// resourceType: 'Patient',
24-
// id: 'context-patient-id',
25-
// };
26-
// const contextProcedure1 = {
27-
// resourceType: 'Procedure',
28-
// id: 'context-procedure-1-id',
29-
// };
30-
// const contextProcedure2 = {
31-
// resourceType: 'Procedure',
32-
// id: 'context-procedure-2-id',
33-
// };
34-
// const context = {
35-
// resourceType: 'Bundle',
36-
// type: 'collection',
37-
// entry: [
38-
// {
39-
// fullUrl: 'context-patient-url',
40-
// resource: contextPatient,
41-
// },
42-
// {
43-
// fullUrl: 'context-procedure-1-url',
44-
// resource: contextProcedure1,
45-
// },
46-
// {
47-
// fullUrl: 'context-procedure-2-url',
48-
// resource: contextProcedure2,
49-
// },
50-
// ],
51-
// };
52-
// const procedures = await extractor.getFHIRProcedures(MOCK_PATIENT_MRN, context);
53-
// expect(fhirProcedureExtractorSpy).not.toHaveBeenCalled();
54-
// expect(procedures).toHaveLength(2);
55-
// expect(procedures[0].resource.id).toEqual(contextProcedure1.id);
56-
// expect(procedures[1].resource.id).toEqual(contextProcedure2.id);
57-
// });
58-
5921
it('should return procedure entries for patient from FHIR search if no context', async () => {
6022
fhirProcedureExtractorSpy.mockClear();
6123
when(fhirProcedureExtractorSpy).calledWith({ mrn: MOCK_PATIENT_MRN, context: {} }).mockReturnValue(exampleProcedureBundle);

test/extractors/fixtures/patient-bundle.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
],
1717
"resource": {
1818
"resourceType": "Patient",
19-
"id": "patient-id",
19+
"id": "EXAMPLE-MRN",
2020
"extension": [
2121
{
2222
"url": "http://hl7.org/fhir/StructureDefinition/us-core-race",

0 commit comments

Comments
 (0)