Skip to content

Commit 21689a2

Browse files
authored
Merge pull request #105 from mcode/csv-module-graceful-fail-on-filter
Csv module graceful fail on filter
2 parents 4abb003 + 13f42b0 commit 21689a2

File tree

8 files changed

+57
-13
lines changed

8 files changed

+57
-13
lines changed

src/cli/app.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,11 @@ async function mcodeApp(Client, fromDate, toDate, pathToConfig, pathToRunLogs, d
9999
const { notificationInfo } = config;
100100
if (notificationInfo) {
101101
const notificationErrors = zipErrors(totalExtractionErrors);
102-
await sendEmailNotification(notificationInfo, notificationErrors, debug);
102+
try {
103+
await sendEmailNotification(notificationInfo, notificationErrors, debug);
104+
} catch (e) {
105+
logger.error(e.message);
106+
}
103107
}
104108
// A run is successful and should be logged when both extraction finishes without fatal errors
105109
// and messages are posted without fatal errors
@@ -115,8 +119,14 @@ async function mcodeApp(Client, fromDate, toDate, pathToConfig, pathToRunLogs, d
115119
const patientConfig = config.extractors.find((e) => e.type === 'CSVPatientExtractor');
116120
if (patientConfig && ('constructorArgs' in patientConfig && 'mask' in patientConfig.constructorArgs)) {
117121
if (patientConfig.constructorArgs.mask.includes('mrn')) {
118-
extractedData.forEach((bundle) => {
119-
maskMRN(bundle);
122+
extractedData.forEach((bundle, i) => {
123+
// NOTE: This may fail to mask MRN-related properties on non-patient resources
124+
// Need to investigate further.
125+
try {
126+
maskMRN(bundle);
127+
} catch (e) {
128+
logger.error(`Bundle ${i + 1}: ${e.message}`);
129+
}
120130
});
121131
}
122132
}

src/extractors/CSVPatientExtractor.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
const _ = require('lodash');
12
const { generateMcodeResources } = require('../templates');
23
const { BaseCSVExtractor } = require('./BaseCSVExtractor');
34
const { getEthnicityDisplay,
45
getRaceCodesystem,
56
getRaceDisplay,
67
maskPatientData } = require('../helpers/patientUtils');
8+
const { getEmptyBundle } = require('../helpers/fhirUtils');
79
const logger = require('../helpers/logger');
810
const { CSVPatientSchema } = require('../helpers/schemas/csv');
911

@@ -55,6 +57,10 @@ class CSVPatientExtractor extends BaseCSVExtractor {
5557
async get({ mrn }) {
5658
// 1. Get all relevant data and do necessary post-processing
5759
const patientData = await this.getPatientData(mrn);
60+
if (_.isEmpty(patientData)) {
61+
logger.warn('No patient data found for this patient');
62+
return getEmptyBundle();
63+
}
5864

5965
// 2. Format data for research study and research subject
6066
const packagedPatientData = joinAndReformatData(patientData);

src/modules/CSVModule.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ class CSVModule {
1313
// return all rows if key and value aren't provided
1414
if (!key && !value) return this.data;
1515
let result = this.data.filter((d) => d[key] === value);
16-
if (result.length === 0) throw new ReferenceError(`CSV Record with provided key '${key}' and value was not found`);
16+
if (result.length === 0) {
17+
logger.warn(`CSV Record with provided key '${key}' and value was not found`);
18+
return result;
19+
}
1720

1821
// If fromDate and toDate is provided, filter out all results that fall outside that timespan
1922
if (fromDate && moment(fromDate).isValid()) result = result.filter((r) => !(r.dateRecorded && moment(fromDate).isAfter(r.dateRecorded)));
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
mrn,trialSubjectID,enrollmentStatus,trialResearchID,trialStatus,trialResearchSystem
2+
123,subjectId-1,potential-candidate,researchId-1,approved,system-1
3+
456,subjectId-2,on-study-intervention,researchId-1,completed,system-2
4+
789,subjectId-3,on-study-observation,researchId-2,active,

test/cli/mcodeExtraction.test.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('mcodeExtraction', () => {
4343
expect(extractedData).toHaveLength(0);
4444
});
4545

46-
it('should result in a successful extraction when non fatal errors are encountered in the client\'s get method', async () => {
46+
it('should succeed in extraction when CSV files do not have data for all patients', async () => {
4747
const testConfig = {
4848
extractors: [
4949
{
@@ -69,10 +69,34 @@ describe('mcodeExtraction', () => {
6969

7070
const { extractedData, successfulExtraction, totalExtractionErrors } = await extractDataForPatients(testPatientIds, testClient, testFromDate, testToDate);
7171
expect(successfulExtraction).toEqual(true);
72+
// Should have data for 3 patients and 0 errors
73+
expect(extractedData).toHaveLength(3);
74+
const flatErrors = flattenErrorValues(totalExtractionErrors);
75+
expect(flatErrors).toHaveLength(0);
76+
});
77+
it('should result in a successful extraction when non fatal errors are encountered in the client\'s get method', async () => {
78+
const testConfig = {
79+
extractors: [
80+
// Should fail when this extractor is run without patient data in context
81+
{
82+
label: 'CTI',
83+
type: 'CSVClinicalTrialInformationExtractor',
84+
constructorArgs: {
85+
filePath: path.join(__dirname, './fixtures/example-clinical-trial-info.csv'),
86+
},
87+
},
88+
],
89+
};
7290

91+
const testClient = new MCODEClient(testConfig);
92+
await testClient.init();
93+
94+
const { extractedData, successfulExtraction, totalExtractionErrors } = await extractDataForPatients(testPatientIds, testClient, testFromDate, testToDate);
95+
expect(successfulExtraction).toEqual(true);
96+
// Should have three (empty) bundles for patients and an error for each patient when extracting CTI
7397
expect(extractedData).toHaveLength(3);
7498
const flatErrors = flattenErrorValues(totalExtractionErrors);
75-
expect(flatErrors).toHaveLength(1);
99+
expect(flatErrors).toHaveLength(3);
76100
});
77101
});
78102
});

test/modules/CSVModule.test.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ test('Returns data with recordedDate before specified to date', async () => {
3131
expect(data).toHaveLength(1);
3232
});
3333

34-
test('Invalid MRN', async () => {
35-
try {
36-
await csvModule.get('mrn', INVALID_MRN);
37-
} catch (e) {
38-
expect(e).toEqual(ReferenceError('CSV Record with provided key \'mrn\' and value was not found'));
39-
}
34+
test('Should return an empty array when key-value pair does not exist', async () => {
35+
const data = await csvModule.get('mrn', INVALID_MRN);
36+
expect(data).toEqual([]);
4037
});
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
mrn,conditionId,diseaseStatusCode,diseaseStatusText,dateOfObservation,evidence,observationStatus,dateRecorded
22
123,conditionId-1,268910001,responding,2019-12-02,363679005|252416005,preliminary,2020-01-10
3-
456,conditionId-2,359746009,stable,2020-01-12,363679005,amended,2020-01-12
43
789,conditionId-2,709137006,not evaluated,2020-04-22,,final,2020-06-10
54
123,conditionId-1,not-asked,,2020-01-12,,,

test/sample-client-data/patient-mrns.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ mrn
22
123
33
456
44
789
5+
1011

0 commit comments

Comments
 (0)