Skip to content

Commit 4be4ced

Browse files
Merge pull request #80 from mcode/csv-adverse-event-extractor
CSV Adverse Event Extractor
2 parents e4719ef + 67d3245 commit 4be4ced

17 files changed

+709
-6
lines changed

config/mcode-csv-config.example.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@
7474
"constructorArgs": {
7575
"filePath": "./data/procedure-information.csv"
7676
}
77+
},
78+
{
79+
"label": "adverseEvent",
80+
"type": "CSVAdverseEventExtractor",
81+
"constructorArgs": {
82+
"filePath": "./test/sample-client-data/adverse-event-information.csv"
83+
}
7784
}
7885
]
7986
}

docs/adverse-event.csv

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
mrn,adverseEventId,adverseEventCode,adverseEventCodeSystem,adverseEventDisplayText,suspectedCauseId,suspectedCauseType,seriousness,seriousnessCodeSystem,seriousnessDisplayText,category,categoryCodeSystem,categoryDisplayText,severity,actuality,studyId,effectiveDate,recordedDate
2-
mrn-full-example,example-id-1,event-code,code-system,code-display,cause-id,resourceType,seriousness-code,code-system,seriousness-display,category-code,code-system,category-dislpay,mild,actual,id,12-09-1994,12-09-1994
3-
mrn-minimal-example,,code-from-default-system,,,,,,,,,,,,,,12-09-1994,
2+
mrn-full-example,example-id-1,event-code,code-system,code-display,cause-id,resourceType,seriousness-code,code-system,seriousness-display,category-code,code-system,category-dislpay,mild,actual,id,1994-12-09,1994-12-09
3+
mrn-two-category-example,example-id-1,event-code,code-system,code-display,cause-id,resourceType,seriousness-code,code-system,seriousness-display,category-code|category-code,code-system|code-system,category-display|category-display,mild,actual,id,1994-12-09,1994-12-09
4+
mrn-minimal-example,,code-from-default-system,,,,,,,,,,,,,,1994-12-09,

src/client/MCODEClient.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { BaseClient } = require('./BaseClient');
22
const {
3+
CSVAdverseEventExtractor,
34
CSVCancerDiseaseStatusExtractor,
45
CSVCancerRelatedMedicationExtractor,
56
CSVClinicalTrialInformationExtractor,
@@ -25,6 +26,7 @@ class MCODEClient extends BaseClient {
2526
constructor({ extractors, commonExtractorArgs, webServiceAuthConfig }) {
2627
super();
2728
this.registerExtractors(
29+
CSVAdverseEventExtractor,
2830
CSVCancerDiseaseStatusExtractor,
2931
CSVCancerRelatedMedicationExtractor,
3032
CSVClinicalTrialInformationExtractor,
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const path = require('path');
2+
const { CSVModule } = require('../modules');
3+
const { generateMcodeResources } = require('../templates');
4+
const { Extractor } = require('./Extractor');
5+
const logger = require('../helpers/logger');
6+
const { formatDateTime } = require('../helpers/dateUtils');
7+
8+
// Formats data to be passed into template-friendly format
9+
function formatData(adverseEventData) {
10+
logger.debug('Reformatting adverse event data from CSV into template format');
11+
return adverseEventData.map((data) => {
12+
const {
13+
mrn, adverseEventId, adverseEventCode, adverseEventCodeSystem, adverseEventDisplayText, suspectedCauseId, suspectedCauseType, seriousness, seriousnessCodeSystem, seriousnessDisplayText,
14+
category, categoryCodeSystem, categoryDisplayText, severity, actuality, studyId, effectiveDate, recordedDate,
15+
} = data;
16+
17+
if (!(mrn && adverseEventCode && effectiveDate)) {
18+
throw new Error('The adverse event is missing an expected attribute. Adverse event code, mrn, and effective date are all required.');
19+
}
20+
21+
const categoryCodes = category.split('|');
22+
const categorySystems = categoryCodeSystem.split('|');
23+
const categoryDisplays = categoryDisplayText.split('|');
24+
25+
if (!(categoryCodes.length === categorySystems.length && categoryCodes.length === categoryDisplays.length)) {
26+
throw new Error('A category attribute on the adverse event is missing a corresponding categoryCodeSystem or categoryDisplayText value.');
27+
}
28+
29+
30+
return {
31+
...(adverseEventId && { id: adverseEventId }),
32+
subjectId: mrn,
33+
code: adverseEventCode,
34+
system: !adverseEventCodeSystem ? 'http://snomed.info/sct' : adverseEventCodeSystem,
35+
display: adverseEventDisplayText,
36+
suspectedCauseId,
37+
suspectedCauseType,
38+
seriousnessCode: seriousness,
39+
seriousnessCodeSystem: !seriousnessCodeSystem ? 'http://terminology.hl7.org/CodeSystem/adverse-event-seriousness' : seriousnessCodeSystem,
40+
seriousnessDisplayText,
41+
category: categoryCodes.map((categoryCode, index) => {
42+
if (!categoryCode) return null;
43+
const categoryCoding = { code: categoryCode, system: categorySystems[index] ? categorySystems[index] : 'http://terminology.hl7.org/CodeSystem/adverse-event-category' };
44+
if (categoryDisplays[index]) categoryCoding.display = categoryDisplays[index];
45+
return categoryCoding;
46+
}),
47+
severity,
48+
actuality: !actuality ? 'actual' : actuality,
49+
studyId,
50+
effectiveDateTime: formatDateTime(effectiveDate),
51+
recordedDateTime: !recordedDate ? null : formatDateTime(recordedDate),
52+
};
53+
});
54+
}
55+
56+
class CSVAdverseEventExtractor extends Extractor {
57+
constructor({ filePath }) {
58+
super();
59+
this.CSVModule = new CSVModule(path.resolve(filePath));
60+
}
61+
62+
async getAdverseEventData(mrn) {
63+
logger.debug('Getting Adverse Event Data');
64+
return this.CSVModule.get('mrn', mrn);
65+
}
66+
67+
async get({ mrn }) {
68+
const adverseEventData = await this.getAdverseEventData(mrn);
69+
const formattedData = formatData(adverseEventData);
70+
71+
return generateMcodeResources('AdverseEvent', formattedData);
72+
}
73+
}
74+
75+
module.exports = {
76+
CSVAdverseEventExtractor,
77+
};

src/extractors/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { BaseFHIRExtractor } = require('./BaseFHIRExtractor');
2+
const { CSVAdverseEventExtractor } = require('./CSVAdverseEventExtractor');
23
const { CSVCancerDiseaseStatusExtractor } = require('./CSVCancerDiseaseStatusExtractor');
34
const { CSVCancerRelatedMedicationExtractor } = require('./CSVCancerRelatedMedicationExtractor');
45
const { CSVClinicalTrialInformationExtractor } = require('./CSVClinicalTrialInformationExtractor');
@@ -24,6 +25,7 @@ const { MCODESurgicalProcedureExtractor } = require('./MCODESurgicalProcedureExt
2425

2526
module.exports = {
2627
BaseFHIRExtractor,
28+
CSVAdverseEventExtractor,
2729
CSVCancerDiseaseStatusExtractor,
2830
CSVCancerRelatedMedicationExtractor,
2931
CSVClinicalTrialInformationExtractor,

src/helpers/templateUtils.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ function ifAllArgsObj(templateFn) {
2424
};
2525
}
2626

27+
// A decorator that modifies a template to only render
28+
// when all of the values on its argumentsArray are not null/empty, returning null otherwise
29+
// NOTE: Will not return null if the argumentsArray/elements are not defined; just checks if they're present with undefined/null values
30+
function ifAllArgsArr(templateFn) {
31+
return (argArr) => {
32+
if (_.some(argArr, (arg) => _.isUndefined(arg) || _.isNull(arg))) {
33+
return null;
34+
}
35+
return templateFn(argArr);
36+
};
37+
}
38+
2739
// A decorator that modifies a template to only render
2840
// when some of its arguments are not null/undefined, returning null otherwise
2941
// NOTE: Will not return null if the arguments are not defined; just checks if they're present with undefined/null values
@@ -48,9 +60,22 @@ function ifSomeArgsObj(templateFn) {
4860
};
4961
}
5062

63+
// A decorator that modifies a template to only render
64+
// when some of the values on its argumentsArray are not null/empty, returning null otherwise
65+
// NOTE: Will not return null if the argumentsArray/elemtns are not defined; just checks if they're present with undefined/null values
66+
function ifSomeArgsArr(templateFn) {
67+
return (argArr) => {
68+
if (_.every(argArr, (arg) => _.isUndefined(arg) || _.isNull(arg))) {
69+
return null;
70+
}
71+
return templateFn(argArr);
72+
};
73+
}
5174
module.exports = {
5275
ifAllArgs,
76+
ifAllArgsArr,
5377
ifAllArgsObj,
5478
ifSomeArgs,
79+
ifSomeArgsArr,
5580
ifSomeArgsObj,
5681
};

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const {
1010
} = require('./cli');
1111
const {
1212
BaseFHIRExtractor,
13+
CSVAdverseEventExtractor,
1314
CSVCancerDiseaseStatusExtractor,
1415
CSVCancerRelatedMedicationExtractor,
1516
CSVClinicalTrialInformationExtractor,
@@ -74,6 +75,7 @@ module.exports = {
7475
BaseClient,
7576
BaseFHIRExtractor,
7677
BaseFHIRModule,
78+
CSVAdverseEventExtractor,
7779
CSVCancerDiseaseStatusExtractor,
7880
CSVCancerRelatedMedicationExtractor,
7981
CSVClinicalTrialInformationExtractor,

src/templates/AdverseEventTemplate.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
const { coding, reference } = require('./snippets');
2+
const { ifAllArgsObj, ifSomeArgsObj, ifAllArgs, ifSomeArgsArr } = require('../helpers/templateUtils');
3+
4+
function eventTemplate(eventCoding) {
5+
return {
6+
event: {
7+
coding: [
8+
coding(eventCoding),
9+
],
10+
},
11+
};
12+
}
13+
14+
function suspectedCauseTemplate({ suspectedCauseId, suspectedCauseType }) {
15+
return {
16+
suspectEntity: [
17+
{
18+
instance:
19+
reference({ id: suspectedCauseId, resourceType: suspectedCauseType }),
20+
},
21+
],
22+
};
23+
}
24+
25+
26+
function seriousnessTemplate(seriousnessCoding) {
27+
return {
28+
seriousness: {
29+
coding: [
30+
coding(seriousnessCoding),
31+
],
32+
},
33+
};
34+
}
35+
36+
function individualCategoryTemplate(category) {
37+
return {
38+
coding: [coding(category),
39+
],
40+
};
41+
}
42+
43+
function categoryArrayTemplate(categoryArr) {
44+
const category = categoryArr.map(individualCategoryTemplate);
45+
return { category };
46+
}
47+
48+
function severityTemplate(severityCode) {
49+
return {
50+
severity: {
51+
coding: [
52+
coding({
53+
code: severityCode,
54+
system: 'http://terminology.hl7.org/CodeSystem/adverse-event-severity',
55+
}),
56+
],
57+
},
58+
};
59+
}
60+
61+
function studyTemplate(studyId) {
62+
return {
63+
study: [
64+
reference({ id: studyId, resourceType: 'ResearchStudy' }),
65+
],
66+
};
67+
}
68+
69+
function recordedDateTemplate(recordedDateTime) {
70+
return {
71+
recordedDate: recordedDateTime,
72+
};
73+
}
74+
75+
function adverseEventTemplate({
76+
id, subjectId, code, system, display, suspectedCauseId, suspectedCauseType, seriousnessCode, seriousnessCodeSystem, seriousnessDisplayText, category,
77+
severity, actuality, studyId, effectiveDateTime, recordedDateTime,
78+
}) {
79+
if (!(subjectId && code && system && effectiveDateTime && actuality)) {
80+
throw Error('Trying to render an AdverseEventTemplate, but a required argument is messing; ensure that subjectId, code, system, actuality, and effectiveDateTime are all present');
81+
}
82+
83+
return {
84+
resourceType: 'AdverseEvent',
85+
id,
86+
subject: reference({ id: subjectId, resourceType: 'Patient' }),
87+
...ifSomeArgsObj(eventTemplate)({ code, system, display }),
88+
...ifAllArgsObj(suspectedCauseTemplate)({ suspectedCauseId, suspectedCauseType }),
89+
...ifSomeArgsObj(seriousnessTemplate)({ code: seriousnessCode, system: seriousnessCodeSystem, display: seriousnessDisplayText }),
90+
...ifSomeArgsArr(categoryArrayTemplate)(category),
91+
...ifAllArgs(severityTemplate)(severity),
92+
actuality,
93+
...ifAllArgs(studyTemplate)(studyId),
94+
date: effectiveDateTime,
95+
...ifAllArgs(recordedDateTemplate)(recordedDateTime),
96+
};
97+
}
98+
99+
module.exports = {
100+
adverseEventTemplate,
101+
};

src/templates/ResourceGenerator.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const _ = require('lodash');
22
const shajs = require('sha.js');
33
const logger = require('../helpers/logger');
44

5+
const { adverseEventTemplate } = require('./AdverseEventTemplate');
56
const { cancerDiseaseStatusTemplate } = require('./CancerDiseaseStatusTemplate');
67
const { cancerRelatedMedicationTemplate } = require('./CancerRelatedMedicationTemplate');
78
const { carePlanWithReviewTemplate } = require('./CarePlanWithReviewTemplate');
@@ -15,6 +16,7 @@ const { stagingTemplate } = require('./StagingTemplate');
1516
const { tnmCategoryTemplate } = require('./TNMCategoryTemplate');
1617

1718
const fhirTemplateLookup = {
19+
AdverseEvent: adverseEventTemplate,
1820
CancerDiseaseStatus: cancerDiseaseStatusTemplate,
1921
CancerRelatedMedication: cancerRelatedMedicationTemplate,
2022
CarePlanWithReview: carePlanWithReviewTemplate,

0 commit comments

Comments
 (0)