Skip to content

Commit 6a28ac8

Browse files
authored
Merge pull request #129 from mcode/extractor-dependencies
Adding implementation of extractor dependencies
2 parents a496089 + 4be0f80 commit 6a28ac8

File tree

4 files changed

+128
-0
lines changed

4 files changed

+128
-0
lines changed

src/client/MCODEClient.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const {
2222
FHIRPatientExtractor,
2323
FHIRProcedureExtractor,
2424
} = require('../extractors');
25+
const { sortExtractors } = require('../helpers/dependencyUtils.js');
2526

2627
class MCODEClient extends BaseClient {
2728
constructor({ extractors, commonExtractorArgs, webServiceAuthConfig }) {
@@ -51,6 +52,21 @@ class MCODEClient extends BaseClient {
5152
);
5253
// Store the extractors defined by the configuration file as local state
5354
this.extractorConfig = extractors;
55+
// Define information about the order and dependencies of extractors
56+
const dependencyInfo = [
57+
{ type: 'CSVPatientExtractor', dependencies: [] },
58+
{ type: 'CSVConditionExtractor', dependencies: ['CSVPatientExtractor'] },
59+
{ type: 'CSVCancerDiseaseStatusExtractor', dependencies: ['CSVPatientExtractor'] },
60+
{ type: 'CSVClinicalTrialInformationExtractor', dependencies: ['CSVPatientExtractor'] },
61+
{ type: 'CSVTreatmentPlanChangeExtractor', dependencies: ['CSVPatientExtractor'] },
62+
{ type: 'CSVStagingExtractor', dependencies: ['CSVPatientExtractor'] },
63+
{ type: 'CSVCancerRelatedMedicationExtractor', dependencies: ['CSVPatientExtractor'] },
64+
{ type: 'CSVProcedureExtractor', dependencies: ['CSVPatientExtractor'] },
65+
{ type: 'CSVObservationExtractor', dependencies: ['CSVPatientExtractor'] },
66+
{ type: 'CSVAdverseEventExtractor', dependencies: ['CSVPatientExtractor'] },
67+
];
68+
// Sort extractors based on order and dependencies
69+
this.extractorConfig = sortExtractors(this.extractorConfig, dependencyInfo);
5470
// Store webServiceAuthConfig if provided`
5571
this.authConfig = webServiceAuthConfig;
5672
this.commonExtractorArgs = {

src/helpers/dependencyUtils.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const logger = require('./logger');
2+
3+
/**
4+
* Checks if any dependencies of extractors are missing.
5+
* If no depedencies are missing, sorts extractors into the correct order.
6+
* @param {Array} extractors array of extractors from the config file
7+
* @param {Array} dependencyInfo array of objects dictacting order of extractors and their dependencies
8+
* @returns {Array} array of extractors sorted into the order defined in dependency info
9+
* Example of dependencyInfo:
10+
* [
11+
* { type: 'CSVPatientExtractor', dependencies: [] },
12+
* { type: 'CSVConditionExtractor', dependencies: ['CSVPatientExtractor'] },
13+
* ...
14+
* ]
15+
*/
16+
function sortExtractors(extractors, dependencyInfo) {
17+
const missing = {};
18+
// Filter dependency info to only extractors in the config
19+
dependencyInfo.filter((e) => extractors.map((x) => x.type).includes(e.type)).forEach((extractor) => {
20+
// For each extractor, check if its dependencies are present
21+
extractor.dependencies.forEach((dependency) => {
22+
if (extractors.filter((e) => e.type === dependency).length === 0) {
23+
if (missing[dependency] === undefined) {
24+
missing[dependency] = [extractor.type];
25+
} else {
26+
missing[dependency].push(extractor.type);
27+
}
28+
}
29+
});
30+
});
31+
// If extractors are missing, alert user which are missing
32+
if (Object.keys(missing).length > 0) {
33+
Object.keys(missing).forEach((extractor) => {
34+
logger.error(`Missing dependency: ${extractor} (required by: ${missing[extractor].join(', ')})`);
35+
});
36+
throw new Error('Some extractors are missing dependencies, see above for details.');
37+
}
38+
// If no missing dependencies, sort extractors into correct order
39+
const sortedExtractors = [...extractors];
40+
const order = dependencyInfo.map((x) => x.type);
41+
sortedExtractors.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
42+
return sortedExtractors;
43+
}
44+
45+
module.exports = {
46+
sortExtractors,
47+
};

test/cli/mcodeExtraction.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ describe('mcodeExtraction', () => {
9292
filePath: path.join(__dirname, './fixtures/example-clinical-trial-info.csv'),
9393
},
9494
},
95+
{
96+
label: 'patient',
97+
type: 'CSVPatientExtractor',
98+
constructorArgs: {
99+
filePath: path.join(__dirname, './fixtures/example-patient.csv'),
100+
},
101+
},
95102
],
96103
};
97104

@@ -105,5 +112,21 @@ describe('mcodeExtraction', () => {
105112
const flatErrors = flattenErrorValues(totalExtractionErrors);
106113
expect(flatErrors).toHaveLength(3);
107114
});
115+
it('should throw error when initialized with missing dependencies', async () => {
116+
const testConfig = {
117+
extractors: [
118+
// Should fail when this extractor is run without patient in config becuase patient is required dependency
119+
{
120+
label: 'CTI',
121+
type: 'CSVClinicalTrialInformationExtractor',
122+
constructorArgs: {
123+
filePath: path.join(__dirname, './fixtures/example-clinical-trial-info.csv'),
124+
},
125+
},
126+
],
127+
};
128+
129+
expect(() => new MCODEClient(testConfig)).toThrow();
130+
});
108131
});
109132
});

test/helpers/dependencyUtils.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const { sortExtractors } = require('../../src/helpers/dependencyUtils.js');
2+
3+
const WRONG_ORDER = [
4+
{ type: 'Extractor3' },
5+
{ type: 'Extractor1' },
6+
{ type: 'Extractor2' },
7+
];
8+
9+
const MISSING = [
10+
{ type: 'Extractor1' },
11+
{ type: 'Extractor3' },
12+
];
13+
14+
const NO_CHANGE = [
15+
{ type: 'Extractor1' },
16+
{ type: 'Extractor2' },
17+
{ type: 'Extractor3' },
18+
];
19+
20+
const DEPENDENCY_INFO = [
21+
{ type: 'Extractor1', dependencies: [] },
22+
{ type: 'Extractor2', dependencies: ['Extractor1'] },
23+
{ type: 'Extractor3', dependencies: ['Extractor1', 'Extractor2'] },
24+
];
25+
26+
describe('sortExtractors', () => {
27+
test('should put extractors in the correct order', () => {
28+
const sorted = sortExtractors(WRONG_ORDER, DEPENDENCY_INFO);
29+
expect(sorted[0].type).toEqual('Extractor1');
30+
expect(sorted[1].type).toEqual('Extractor2');
31+
expect(sorted[2].type).toEqual('Extractor3');
32+
});
33+
34+
test('should change nothing if all extractors are in order with all dependencies', () => {
35+
const unchanged = sortExtractors(NO_CHANGE, DEPENDENCY_INFO);
36+
expect(unchanged).toEqual(NO_CHANGE);
37+
});
38+
39+
test('should fail when missing dependencies', () => {
40+
expect(() => sortExtractors(MISSING, DEPENDENCY_INFO)).toThrow();
41+
});
42+
});

0 commit comments

Comments
 (0)