Skip to content

Commit

Permalink
Adding searchParameters to fhir extractor construction args to allow …
Browse files Browse the repository at this point in the history
…for customizing the searches to servers to scope results. This will allow us to target specific Resources as best we can depending on the fhir server being accessed.
  • Loading branch information
rdingwell committed Nov 6, 2023
1 parent 9dca83e commit c57eb6a
Show file tree
Hide file tree
Showing 18 changed files with 50 additions and 185 deletions.
6 changes: 4 additions & 2 deletions src/extractors/BaseFHIRExtractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ const { getPatientFromContext } = require('../helpers/contextUtils');
const logger = require('../helpers/logger');

class BaseFHIRExtractor extends Extractor {
constructor({ baseFhirUrl, requestHeaders, version, resourceType }) {
constructor({ baseFhirUrl, requestHeaders, version, resourceType, searchParameters={} }) {
super();
this.resourceType = resourceType;
this.version = version;
this.baseFHIRModule = new BaseFHIRModule(baseFhirUrl, requestHeaders);
this.searchParameters = searchParameters;
}

updateRequestHeaders(newHeaders) {
Expand All @@ -21,7 +22,8 @@ class BaseFHIRExtractor extends Extractor {
// NOTE: Async because other extractors that extend this may need to make async lookups in the future
async parametrizeArgsForFHIRModule({ context }) {
const patient = getPatientFromContext(context);
return { patient: patient.id };

return { ...this.searchParameters, patient: patient.id };
}
/* eslint-enable class-methods-use-this */

Expand Down
4 changes: 2 additions & 2 deletions src/extractors/FHIRAdverseEventExtractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const logger = require('../helpers/logger');
const BASE_STUDY = ''; // No base study specified

class FHIRAdverseEventExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version, study }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, study, searchParameters }) {
super({ baseFhirUrl, requestHeaders, version,searchParameters });
this.resourceType = 'AdverseEvent';
this.study = study || BASE_STUDY;
}
Expand Down
13 changes: 2 additions & 11 deletions src/extractors/FHIRAllergyIntoleranceExtractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,11 @@ const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');
const BASE_CLINICAL_STATUS = 'active';

class FHIRAllergyIntoleranceExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version, clinicalStatus }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version,searchParameters }) {
super({ baseFhirUrl, requestHeaders, version,searchParameters });
this.resourceType = 'AllergyIntolerance';
this.clinicalStatus = clinicalStatus || BASE_CLINICAL_STATUS;
}

// In addition to default parametrization, add clinical status
async parametrizeArgsForFHIRModule({ context }) {
const paramsWithID = await super.parametrizeArgsForFHIRModule({ context });
return {
...paramsWithID,
'clinical-status': this.clinicalStatus,
};
}
}

module.exports = {
Expand Down
15 changes: 2 additions & 13 deletions src/extractors/FHIRConditionExtractor.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');

const BASE_CATEGORIES = 'problem-list-item';

class FHIRConditionExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version, category }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, searchParameters}) {
super({ baseFhirUrl, requestHeaders, version, searchParameters });
this.resourceType = 'Condition';
this.category = category || BASE_CATEGORIES;
}

// In addition to default parametrization, add category
async parametrizeArgsForFHIRModule({ context }) {
const paramsWithID = await super.parametrizeArgsForFHIRModule({ context });
return {
...paramsWithID,
category: this.category,
};
}
}

module.exports = {
Expand Down
4 changes: 2 additions & 2 deletions src/extractors/FHIRDocumentReferenceExtractor.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');

class FHIRDocumentReferenceExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, searchParameters }) {
super({ baseFhirUrl, requestHeaders, version,searchParameters });
this.resourceType = 'DocumentReference';
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/extractors/FHIREncounterExtractor.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');

class FHIREncounterExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, searchParameters }) {
super({ baseFhirUrl, requestHeaders, version, searchParameters });
this.resourceType = 'Encounter';
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/extractors/FHIRMedicationOrderExtractor.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');

class FHIRMedicationOrderExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, searchParameters }) {
super({ baseFhirUrl, requestHeaders, version, searchParameters });
this.resourceType = 'MedicationOrder';
}
}
Expand Down
17 changes: 2 additions & 15 deletions src/extractors/FHIRMedicationRequestExtractor.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');

const BASE_STATUSES = ''; // No status specified, returns all statuses (on-hold, completed, stopped, active)

class FHIRMedicationRequestExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version, status }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, status, searchParameters }) {
super({ baseFhirUrl, requestHeaders, version, searchParameters });
this.resourceType = 'MedicationRequest';
this.status = status || BASE_STATUSES;
}

// In addition to default parametrization, add status if specified
async parametrizeArgsForFHIRModule({ context }) {
const paramsWithID = await super.parametrizeArgsForFHIRModule({ context });
// Only add status to parameters if it has been specified
return {
...paramsWithID,
...(this.status && { status: this.status }),
};
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/extractors/FHIRMedicationStatementExtractor.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');

class FHIRMedicationStatementExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, searchParameters }) {
super({ baseFhirUrl, requestHeaders, version, searchParameters });
this.resourceType = 'MedicationStatement';
}
}
Expand Down
15 changes: 2 additions & 13 deletions src/extractors/FHIRObservationExtractor.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');

const BASE_CATEGORIES = 'laboratory,vital-signs,social-history,LDA,core-characteristics';

class FHIRObservationExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version, category }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, category, searchParameters }) {
super({ baseFhirUrl, requestHeaders, version, searchParameters });
this.resourceType = 'Observation';
this.category = category || BASE_CATEGORIES;
}

// In addition to default parametrization, add category
async parametrizeArgsForFHIRModule({ context }) {
const paramsWithID = await super.parametrizeArgsForFHIRModule({ context });
return {
...paramsWithID,
category: this.category,
};
}
}

module.exports = {
Expand Down
4 changes: 2 additions & 2 deletions src/extractors/FHIRPatientExtractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');
const { maskPatientData } = require('../helpers/patientUtils.js');

class FHIRPatientExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version, mask = [] }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, mask = [], searchParameters }) {
super({ baseFhirUrl, requestHeaders, version, searchParameters });
this.resourceType = 'Patient';
this.mask = mask;
}
Expand Down
4 changes: 2 additions & 2 deletions src/extractors/FHIRProcedureExtractor.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');

class FHIRProcedureExtractor extends BaseFHIRExtractor {
constructor({ baseFhirUrl, requestHeaders, version }) {
super({ baseFhirUrl, requestHeaders, version });
constructor({ baseFhirUrl, requestHeaders, version, searchParameters }) {
super({ baseFhirUrl, requestHeaders, version, searchParameters });
this.resourceType = 'Procedure';
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/schemas/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
"title": "Request Headers",
"type": "object"
},
"queryParameters": {
"title": "Query Parameters",
"type": "object"
},
"csvParse" : {
"title": "CSV Parse",
"type": "object",
Expand Down
19 changes: 17 additions & 2 deletions test/extractors/BaseFHIRExtractor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ const MOCK_URL = 'http://localhost';
const MOCK_REQUEST_HEADERS = {
Accept: 'application/json',
};
const MOCK_SEARCH_PARAMS = {"_include": "Condition::subject",
"category" : "problem-list-item",
"status" : "final"}
const MOCK_RESOURCE_TYPE = 'Condition';
const MOCK_PATIENT_MRN = 'EXAMPLE-MRN';
const MOCK_CONTEXT = {
Expand All @@ -21,16 +24,20 @@ const MOCK_CONTEXT = {
};

// Create extractor and destructure to mock responses on modules
const baseFHIRExtractor = new BaseFHIRExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_REQUEST_HEADERS, resourceType: MOCK_RESOURCE_TYPE });
const baseFHIRExtractor = new BaseFHIRExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_REQUEST_HEADERS, resourceType: MOCK_RESOURCE_TYPE , searchParameters: MOCK_SEARCH_PARAMS});
const { baseFHIRModule } = baseFHIRExtractor;

// Spies for mocking
const baseFHIRModuleSearchSpy = jest.spyOn(baseFHIRModule, 'search');
const moduleRequestHeadersSpy = jest.spyOn(baseFHIRModule, 'updateRequestHeaders');

// Ensure that data is returned for condition
debugger
when(baseFHIRModuleSearchSpy)
.calledWith('Condition', { patient: examplePatientBundle.entry[0].resource.id })
.calledWith('Condition', { patient: examplePatientBundle.entry[0].resource.id,
_include: MOCK_SEARCH_PARAMS._include,
category: MOCK_SEARCH_PARAMS.category,
status: MOCK_SEARCH_PARAMS.status })
.mockReturnValue(exampleConditionBundle);

// Tests
Expand All @@ -47,6 +54,13 @@ describe('BaseFhirExtractor', () => {
expect(baseFHIRModuleSearchSpy).not.toHaveBeenCalled();
expect(paramsBasedOnContext).toHaveProperty('patient');
expect(paramsBasedOnContext.patient).toEqual(MOCK_CONTEXT.entry[0].resource.id);
expect(paramsBasedOnContext).toHaveProperty("_include");
expect(paramsBasedOnContext._include).toEqual(MOCK_SEARCH_PARAMS._include);
expect(paramsBasedOnContext).toHaveProperty("category");
expect(paramsBasedOnContext.category).toEqual(MOCK_SEARCH_PARAMS.category);
expect(paramsBasedOnContext).toHaveProperty("status");
expect(paramsBasedOnContext.status).toEqual(MOCK_SEARCH_PARAMS.status);

});

test('parametrizeArgsForFHIRModule throws an error if context has no relevant ID', async () => {
Expand All @@ -56,6 +70,7 @@ describe('BaseFhirExtractor', () => {
});

test('get should return a condition resource', async () => {
debugger
const data = await baseFHIRExtractor.get({ context: MOCK_CONTEXT });
expect(data.resourceType).toEqual('Bundle');
expect(data.entry).toBeDefined();
Expand Down
30 changes: 0 additions & 30 deletions test/extractors/FHIRAllergyIntoleranceExtractor.test.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,15 @@
const rewire = require('rewire');
const { FHIRAllergyIntoleranceExtractor } = require('../../src/extractors/FHIRAllergyIntoleranceExtractor.js');

const FHIRAllergyIntoleranceExtractorRewired = rewire('../../src/extractors/FHIRAllergyIntoleranceExtractor.js');
const MOCK_URL = 'http://example.com';
const MOCK_HEADERS = {};
const MOCK_MRN = '123456789';
const MOCK_CLINICAL_STATUS = 'status1,status2';
const MOCK_CONTEXT = {
resourceType: 'Bundle',
entry: [
{
fullUrl: 'context-url',
resource: { resourceType: 'Patient', id: MOCK_MRN },
},
],
};

const extractor = new FHIRAllergyIntoleranceExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS });
const extractorWithClinicalStatus = new FHIRAllergyIntoleranceExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS, clinicalStatus: MOCK_CLINICAL_STATUS });
const baseClinicalStatus = FHIRAllergyIntoleranceExtractorRewired.__get__('BASE_CLINICAL_STATUS');

describe('FHIRAllergyIntoleranceExtractor', () => {
describe('Constructor', () => {
test('sets resourceType as AllergyIntolerance', () => {
expect(extractor.resourceType).toEqual('AllergyIntolerance');
});

test('sets clinical status based on BASE_CLINICAL_STATUS', () => {
expect(extractor.clinicalStatus).toEqual(baseClinicalStatus);
});

test('sets clinical status if provided', () => {
expect(extractorWithClinicalStatus.clinicalStatus).toEqual(MOCK_CLINICAL_STATUS);
});
});

describe('parametrizeArgsForFHIRModule', () => {
test('should add category to param values', async () => {
const params = await extractor.parametrizeArgsForFHIRModule({ context: MOCK_CONTEXT });
expect(params).toHaveProperty('clinical-status');
expect(params['clinical-status']).toEqual(baseClinicalStatus);
});
});
});
23 changes: 1 addition & 22 deletions test/extractors/FHIRConditionExtractor.test.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,16 @@
const rewire = require('rewire');
const { FHIRConditionExtractor } = require('../../src/extractors/FHIRConditionExtractor.js');
const MOCK_CONTEXT = require('./fixtures/context-with-patient.json');

const FHIRConditionExtractorRewired = rewire('../../src/extractors/FHIRConditionExtractor');
const MOCK_URL = 'http://example.com';
const MOCK_HEADERS = {};
const MOCK_CATEGORIES = 'category1,category2';



const extractor = new FHIRConditionExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS });
const extractorWithCategories = new FHIRConditionExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS, category: MOCK_CATEGORIES });
const baseCategories = FHIRConditionExtractorRewired.__get__('BASE_CATEGORIES');

describe('FHIRConditionExtractor', () => {
describe('Constructor', () => {
test('sets resourceType to Condition', () => {
expect(extractor.resourceType).toEqual('Condition');
});

test('sets category based on BASE_CATEGORIES if not provided', () => {
expect(extractor.category).toEqual(baseCategories);
});

test('sets category if provided', () => {
expect(extractorWithCategories.category).toEqual(MOCK_CATEGORIES);
});
});

describe('parametrizeArgsForFHIRModule', () => {
test('should add category to param values', async () => {
const params = await extractor.parametrizeArgsForFHIRModule({ context: MOCK_CONTEXT });
expect(params).toHaveProperty('category');
expect(params.category).toEqual(baseCategories);
});
});
});
35 changes: 0 additions & 35 deletions test/extractors/FHIRMedicationRequestExtractor.test.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,14 @@
const rewire = require('rewire');
const { FHIRMedicationRequestExtractor } = require('../../src/extractors/FHIRMedicationRequestExtractor.js');

const FHIRMedicationRequestExtractorRewired = rewire('../../src/extractors/FHIRMedicationRequestExtractor.js');
const MOCK_URL = 'http://example.com';
const MOCK_HEADERS = {};
const MOCK_MRN = '123456789';
const MOCK_STATUSES = 'status1,status2';
const MOCK_CONTEXT = {
resourceType: 'Bundle',
entry: [
{
fullUrl: 'context-url',
resource: { resourceType: 'Patient', id: MOCK_MRN },
},
],
};

// Construct extractor and create spies for mocking responses
const extractor = new FHIRMedicationRequestExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS });
const extractorWithStatuses = new FHIRMedicationRequestExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS, status: MOCK_STATUSES });
const baseStatuses = FHIRMedicationRequestExtractorRewired.__get__('BASE_STATUSES');

describe('FHIRMedicationRequestExtractor', () => {
describe('Constructor', () => {
test('sets resourceType as MedicationRequest', () => {
expect(extractor.resourceType).toEqual('MedicationRequest');
});
test('sets status based on BASE_STATUS if not provided', () => {
expect(extractor.status).toEqual(baseStatuses);
});
test('sets status if provided', () => {
expect(extractorWithStatuses.status).toEqual(MOCK_STATUSES);
});
});

describe('parametrizeArgsForFHIRModule', () => {
test('should not add status when not set to param values', async () => {
const params = await extractor.parametrizeArgsForFHIRModule({ context: MOCK_CONTEXT });
expect(params).not.toHaveProperty('status');
});

test('should add status when set to param values', async () => {
const params = await extractorWithStatuses.parametrizeArgsForFHIRModule({ context: MOCK_CONTEXT });
expect(params).toHaveProperty('status');
expect(params.status).toEqual(extractorWithStatuses.status);
});
});
});
Loading

0 comments on commit c57eb6a

Please sign in to comment.