Skip to content

Commit a81f4f8

Browse files
authored
Merge pull request #108 from mcode/csv-extractor-helpful-error-messages
Numerous Extractor Updates
2 parents b9471c2 + 98c5b37 commit a81f4f8

38 files changed

+409
-307
lines changed

config/csv.config.example.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@
1313
},
1414
"extractors": [
1515
{
16-
"label": "condition",
17-
"type": "CSVConditionExtractor",
16+
"label": "patient",
17+
"type": "CSVPatientExtractor",
1818
"constructorArgs": {
19-
"filePath": "./data/condition-information.csv"
19+
"filePath": "./data/patient-information.csv"
2020
}
2121
},
2222
{
23-
"label": "patient",
24-
"type": "CSVPatientExtractor",
23+
"label": "condition",
24+
"type": "CSVConditionExtractor",
2525
"constructorArgs": {
26-
"filePath": "./data/patient-information.csv"
26+
"filePath": "./data/condition-information.csv"
2727
}
2828
},
2929
{

src/extractors/CSVAdverseEventExtractor.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
const { BaseCSVExtractor } = require('./BaseCSVExtractor');
22
const { generateMcodeResources } = require('../templates');
3-
const logger = require('../helpers/logger');
3+
const { getEmptyBundle } = require('../helpers/fhirUtils');
4+
const { getPatientFromContext } = require('../helpers/contextUtils');
45
const { formatDateTime } = require('../helpers/dateUtils');
6+
const logger = require('../helpers/logger');
57

68
// Formats data to be passed into template-friendly format
7-
function formatData(adverseEventData) {
9+
function formatData(adverseEventData, patientId) {
810
logger.debug('Reformatting adverse event data from CSV into template format');
911
return adverseEventData.map((data) => {
1012
const {
11-
mrn, adverseEventId, adverseEventCode, adverseEventCodeSystem, adverseEventDisplayText, suspectedCauseId, suspectedCauseType, seriousness, seriousnessCodeSystem, seriousnessDisplayText,
13+
adverseEventId, adverseEventCode, adverseEventCodeSystem, adverseEventDisplayText, suspectedCauseId, suspectedCauseType, seriousness, seriousnessCodeSystem, seriousnessDisplayText,
1214
category, categoryCodeSystem, categoryDisplayText, severity, actuality, studyId, effectiveDate, recordedDate,
1315
} = data;
1416

15-
if (!(mrn && adverseEventCode && effectiveDate)) {
16-
throw new Error('The adverse event is missing an expected attribute. Adverse event code, mrn, and effective date are all required.');
17+
if (!(adverseEventCode && effectiveDate)) {
18+
throw new Error('The adverse event is missing an expected attribute. Adverse event code and effective date are all required.');
1719
}
1820

1921
const categoryCodes = category.split('|');
@@ -27,7 +29,7 @@ function formatData(adverseEventData) {
2729

2830
return {
2931
...(adverseEventId && { id: adverseEventId }),
30-
subjectId: mrn,
32+
subjectId: patientId,
3133
code: adverseEventCode,
3234
system: !adverseEventCodeSystem ? 'http://snomed.info/sct' : adverseEventCodeSystem,
3335
display: adverseEventDisplayText,
@@ -61,10 +63,18 @@ class CSVAdverseEventExtractor extends BaseCSVExtractor {
6163
return this.csvModule.get('mrn', mrn);
6264
}
6365

64-
async get({ mrn }) {
66+
async get({ mrn, context }) {
6567
const adverseEventData = await this.getAdverseEventData(mrn);
66-
const formattedData = formatData(adverseEventData);
68+
if (adverseEventData.length === 0) {
69+
logger.warn('No adverse event data found for patient');
70+
return getEmptyBundle();
71+
}
72+
const patientId = getPatientFromContext(context).id;
73+
74+
// Reformat data
75+
const formattedData = formatData(adverseEventData, patientId);
6776

77+
// Fill templates
6878
return generateMcodeResources('AdverseEvent', formattedData);
6979
}
7080
}

src/extractors/CSVCancerDiseaseStatusExtractor.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { BaseCSVExtractor } = require('./BaseCSVExtractor');
22
const { formatDateTime } = require('../helpers/dateUtils');
33
const { getDiseaseStatusDisplay, getDiseaseStatusEvidenceDisplay } = require('../helpers/diseaseStatusUtils');
44
const { generateMcodeResources } = require('../templates');
5+
const { getPatientFromContext } = require('../helpers/contextUtils');
56
const { getEmptyBundle } = require('../helpers/fhirUtils');
67
const logger = require('../helpers/logger');
78
const { CSVCancerDiseaseStatusSchema } = require('../helpers/schemas/csv');
@@ -12,12 +13,12 @@ class CSVCancerDiseaseStatusExtractor extends BaseCSVExtractor {
1213
this.implementation = implementation;
1314
}
1415

15-
joinAndReformatData(arrOfDiseaseStatusData) {
16+
joinAndReformatData(arrOfDiseaseStatusData, patientId) {
1617
logger.debug('Reformatting disease status data from CSV into template format');
1718
// Check the shape of the data
1819
arrOfDiseaseStatusData.forEach((record) => {
19-
if (!record.mrn || !record.conditionId || !record.diseaseStatusCode || !record.dateOfObservation) {
20-
throw new Error('DiseaseStatusData missing an expected property: mrn, conditionId, diseaseStatusCode, and dateOfObservation are required.');
20+
if (!record.conditionId || !record.diseaseStatusCode || !record.dateOfObservation) {
21+
throw new Error('DiseaseStatusData missing an expected property: conditionId, diseaseStatusCode, and dateOfObservation are required.');
2122
}
2223
});
2324
const evidenceDelimiter = '|';
@@ -29,7 +30,7 @@ class CSVCancerDiseaseStatusExtractor extends BaseCSVExtractor {
2930
display: record.diseaseStatusText ? record.diseaseStatusText : getDiseaseStatusDisplay(record.diseaseStatusCode, this.implementation),
3031
},
3132
subject: {
32-
id: record.mrn,
33+
id: patientId,
3334
},
3435
condition: {
3536
id: record.conditionId,
@@ -47,16 +48,17 @@ class CSVCancerDiseaseStatusExtractor extends BaseCSVExtractor {
4748
return this.csvModule.get('mrn', mrn, fromDate, toDate);
4849
}
4950

50-
async get({ mrn, fromDate, toDate }) {
51+
async get({ mrn, context, fromDate, toDate }) {
5152
// 1. Get all relevant data and do necessary post-processing
5253
const diseaseStatusData = await this.getDiseaseStatusData(mrn, fromDate, toDate);
5354
if (diseaseStatusData.length === 0) {
5455
logger.warn('No disease status data found for patient');
5556
return getEmptyBundle();
5657
}
58+
const patientId = getPatientFromContext(context).id;
5759

5860
// 2. Format data for research study and research subject
59-
const packagedDiseaseStatusData = this.joinAndReformatData(diseaseStatusData);
61+
const packagedDiseaseStatusData = this.joinAndReformatData(diseaseStatusData, patientId);
6062

6163
// 3. Generate FHIR Resources
6264
const resources = generateMcodeResources('CancerDiseaseStatus', packagedDiseaseStatusData);

src/extractors/CSVCancerRelatedMedicationExtractor.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
const { BaseCSVExtractor } = require('./BaseCSVExtractor');
22
const { generateMcodeResources } = require('../templates');
3-
const logger = require('../helpers/logger');
3+
const { getPatientFromContext } = require('../helpers/contextUtils');
4+
const { getEmptyBundle } = require('../helpers/fhirUtils');
45
const { formatDateTime } = require('../helpers/dateUtils');
6+
const logger = require('../helpers/logger');
57

68

7-
function formatData(medicationData) {
9+
function formatData(medicationData, patientId) {
810
logger.debug('Reformatting cancer-related medication data from CSV into template format');
911

1012
return medicationData.map((medication) => {
1113
const {
12-
mrn, medicationId, code, codeSystem, displayText, startDate, endDate, treatmentReasonCode, treatmentReasonCodeSystem, treatmentReasonDisplayText, treatmentIntent, status,
14+
medicationId, code, codeSystem, displayText, startDate, endDate, treatmentReasonCode, treatmentReasonCodeSystem, treatmentReasonDisplayText, treatmentIntent, status,
1315
} = medication;
1416

15-
if (!(mrn && code && codeSystem && status)) {
16-
throw new Error('The cancer-related medication is missing an expected element; mrn, code, code system, and status are all required values.');
17+
if (!(code && codeSystem && status)) {
18+
throw new Error('The cancer-related medication is missing an expected element; code, code system, and status are all required values.');
1719
}
1820

1921
return {
20-
mrn,
2122
...(medicationId && { id: medicationId }),
23+
subjectId: patientId,
2224
code,
2325
codeSystem,
2426
displayText,
@@ -43,10 +45,18 @@ class CSVCancerRelatedMedicationExtractor extends BaseCSVExtractor {
4345
return this.csvModule.get('mrn', mrn);
4446
}
4547

46-
async get({ mrn }) {
48+
async get({ mrn, context }) {
4749
const medicationData = await this.getMedicationData(mrn);
48-
const formattedData = formatData(medicationData);
50+
if (medicationData.length === 0) {
51+
logger.warn('No medication data found for patient');
52+
return getEmptyBundle();
53+
}
54+
const patientId = getPatientFromContext(context).id;
55+
56+
// Reformat data
57+
const formattedData = formatData(medicationData, patientId);
4958

59+
// Fill templates
5060
return generateMcodeResources('CancerRelatedMedication', formattedData);
5161
}
5262
}

src/extractors/CSVClinicalTrialInformationExtractor.js

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
1+
const _ = require('lodash');
12
const { BaseCSVExtractor } = require('./BaseCSVExtractor');
2-
const { firstEntryInBundle, getBundleResourcesByType } = require('../helpers/fhirUtils');
3+
const { firstEntryInBundle, getEmptyBundle } = require('../helpers/fhirUtils');
4+
const { getPatientFromContext } = require('../helpers/contextUtils');
35
const { generateMcodeResources } = require('../templates');
46
const logger = require('../helpers/logger');
57
const { CSVClinicalTrialInformationSchema } = require('../helpers/schemas/csv');
68

7-
function getPatientId(context) {
8-
const patientInContext = getBundleResourcesByType(context, 'Patient', {}, true);
9-
if (patientInContext) {
10-
logger.debug('Patient resource found in context.');
11-
return patientInContext.id;
12-
}
13-
14-
logger.debug('No patient resource found in context.');
15-
return undefined;
16-
}
179

1810
class CSVClinicalTrialInformationExtractor extends BaseCSVExtractor {
1911
constructor({ filePath, clinicalSiteID, clinicalSiteSystem }) {
@@ -23,15 +15,15 @@ class CSVClinicalTrialInformationExtractor extends BaseCSVExtractor {
2315
this.clinicalSiteSystem = clinicalSiteSystem;
2416
}
2517

26-
joinClinicalTrialData(patientId, clinicalTrialData) {
18+
joinClinicalTrialData(clinicalTrialData, patientId) {
2719
logger.debug('Reformatting clinical trial data from CSV into template format');
2820
const {
2921
trialSubjectID, enrollmentStatus, trialResearchID, trialStatus, trialResearchSystem,
3022
} = clinicalTrialData;
3123
const { clinicalSiteID, clinicalSiteSystem } = this;
3224

33-
if (!(patientId && clinicalSiteID && trialSubjectID && enrollmentStatus && trialResearchID && trialStatus)) {
34-
throw new Error('Clinical trial missing an expected property: patientId, clinicalSiteID, trialSubjectID, enrollmentStatus, trialResearchID, and trialStatus are required.');
25+
if (!(clinicalSiteID && trialSubjectID && enrollmentStatus && trialResearchID && trialStatus)) {
26+
throw new Error('Clinical trial missing an expected property: clinicalSiteID, trialSubjectID, enrollmentStatus, trialResearchID, and trialStatus are required.');
3527
}
3628

3729
// Need separate data objects for ResearchSubject and ResearchStudy so that they get different resource ids
@@ -61,11 +53,15 @@ class CSVClinicalTrialInformationExtractor extends BaseCSVExtractor {
6153
}
6254

6355
async get({ mrn, context }) {
64-
const patientId = getPatientId(context) || mrn;
6556
const clinicalTrialData = await this.getClinicalTrialData(mrn);
57+
if (_.isEmpty(clinicalTrialData)) {
58+
logger.warn('No clinicalTrial record found for patient');
59+
return getEmptyBundle();
60+
}
61+
const patientId = getPatientFromContext(context).id;
6662

6763
// Format data for research study and research subject
68-
const formattedData = this.joinClinicalTrialData(patientId, clinicalTrialData);
64+
const formattedData = this.joinClinicalTrialData(clinicalTrialData, patientId);
6965
const { formattedDataSubject, formattedDataStudy } = formattedData;
7066

7167
// Generate ResearchSubject and ResearchStudy resources and combine into one bundle to return

src/extractors/CSVConditionExtractor.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
const { BaseCSVExtractor } = require('./BaseCSVExtractor');
22
const { generateMcodeResources } = require('../templates');
3-
const logger = require('../helpers/logger');
3+
const { getPatientFromContext } = require('../helpers/contextUtils');
4+
const { getEmptyBundle } = require('../helpers/fhirUtils');
45
const { formatDateTime } = require('../helpers/dateUtils');
56
const { CSVConditionSchema } = require('../helpers/schemas/csv');
7+
const logger = require('../helpers/logger');
68

79
// Formats data to be passed into template-friendly format
8-
function formatData(conditionData) {
10+
function formatData(conditionData, patientId) {
911
logger.debug('Reformatting condition data from CSV into template format');
1012
return conditionData.map((data) => {
1113
const {
12-
mrn, conditionId, codeSystem, code, displayName, category, dateOfDiagnosis, clinicalStatus, verificationStatus, bodySite, laterality, histology,
14+
conditionId, codeSystem, code, displayName, category, dateOfDiagnosis, clinicalStatus, verificationStatus, bodySite, laterality, histology,
1315
} = data;
1416

15-
if (!(conditionId && mrn && codeSystem && code && category)) {
16-
throw new Error('The condition is missing an expected attribute. Condition id, mrn, code system, code, and category are all required.');
17+
if (!(conditionId && codeSystem && code && category)) {
18+
throw new Error('The condition is missing an expected attribute. Condition id, code system, code, and category are all required.');
1719
}
1820
return {
1921
id: conditionId,
2022
subject: {
21-
id: mrn,
23+
id: patientId,
2224
},
2325
code: {
2426
code,
@@ -46,10 +48,18 @@ class CSVConditionExtractor extends BaseCSVExtractor {
4648
return this.csvModule.get('mrn', mrn);
4749
}
4850

49-
async get({ mrn }) {
51+
async get({ mrn, context }) {
5052
const conditionData = await this.getConditionData(mrn);
51-
const formattedData = formatData(conditionData);
53+
if (conditionData.length === 0) {
54+
logger.warn('No condition data found for patient');
55+
return getEmptyBundle();
56+
}
57+
const patientId = getPatientFromContext(context).id;
58+
59+
// Reformat data
60+
const formattedData = formatData(conditionData, patientId);
5261

62+
// Fill templates
5363
return generateMcodeResources('Condition', formattedData);
5464
}
5565
}

src/extractors/CSVObservationExtractor.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
const { BaseCSVExtractor } = require('./BaseCSVExtractor');
22
const { generateMcodeResources } = require('../templates');
3-
const logger = require('../helpers/logger');
3+
const { getPatientFromContext } = require('../helpers/contextUtils');
4+
const { getEmptyBundle } = require('../helpers/fhirUtils');
45
const { formatDateTime } = require('../helpers/dateUtils');
6+
const logger = require('../helpers/logger');
57

6-
function formatData(observationData) {
8+
function formatData(observationData, patientId) {
79
logger.debug('Reformatting observation data from CSV into template format');
810
return observationData.map((data) => {
911
const {
10-
mrn, observationId, status, code, codeSystem, displayName, value, valueCodeSystem, effectiveDate, bodySite, laterality,
12+
observationId, status, code, codeSystem, displayName, value, valueCodeSystem, effectiveDate, bodySite, laterality,
1113
} = data;
1214

13-
if (!mrn || !observationId || !status || !code || !codeSystem || !value || !effectiveDate) {
14-
throw new Error('The observation is missing an expected attribute. Observation id, mrn, status, code, code system, value, and effective date are all required.');
15+
if (!observationId || !status || !code || !codeSystem || !value || !effectiveDate) {
16+
throw new Error('The observation is missing an expected attribute. Observation id, status, code, code system, value, and effective date are all required.');
1517
}
1618

1719
return {
1820
id: observationId,
19-
subjectId: mrn,
21+
subjectId: patientId,
2022
status,
2123
code,
2224
system: codeSystem,
@@ -40,10 +42,18 @@ class CSVObservationExtractor extends BaseCSVExtractor {
4042
return this.csvModule.get('mrn', mrn);
4143
}
4244

43-
async get({ mrn }) {
45+
async get({ mrn, context }) {
4446
const observationData = await this.getObservationData(mrn);
45-
const formattedData = formatData(observationData);
47+
if (observationData.length === 0) {
48+
logger.warn('No observation data found for patient');
49+
return getEmptyBundle();
50+
}
51+
const patientId = getPatientFromContext(context).id;
52+
53+
// Reformat data
54+
const formattedData = formatData(observationData, patientId);
4655

56+
// Fill template
4757
return generateMcodeResources('Observation', formattedData);
4858
}
4959
}

src/extractors/CSVPatientExtractor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const { getEthnicityDisplay,
66
getRaceDisplay,
77
maskPatientData } = require('../helpers/patientUtils');
88
const { getEmptyBundle } = require('../helpers/fhirUtils');
9-
const logger = require('../helpers/logger');
109
const { CSVPatientSchema } = require('../helpers/schemas/csv');
10+
const logger = require('../helpers/logger');
1111

1212
function joinAndReformatData(patientData) {
1313
logger.debug('Reformatting patient data from CSV into template format');

0 commit comments

Comments
 (0)