Skip to content

Commit 4574c69

Browse files
committed
Category changes + test cases
1 parent 8b50fd2 commit 4574c69

11 files changed

+523
-18
lines changed

src/extractors/CSVAdverseEventExtractor.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ function formatData(adverseEventData) {
1717
if (!(mrn && adverseEventCode && effectiveDate)) {
1818
throw new Error('The adverse event is missing an expected attribute. Adverse event code, mrn, and effective date are all required.');
1919
}
20+
21+
const categoryCodes = category.split('|');
22+
const categorySystems = categoryCodeSystem.split('|');
23+
const categoryDisplays = categoryDisplayText.split('|');
24+
25+
2026
return {
2127
id: adverseEventId,
2228
subjectId: mrn,
@@ -26,11 +32,14 @@ function formatData(adverseEventData) {
2632
suspectedCauseId,
2733
suspectedCauseType,
2834
seriousnessCode: seriousness,
29-
seriousnessCodeSystem: !seriousnessCodeSystem ? 'http://terminology.hl7.org/CodeSystem/adverse-event-seriousness' : seriousnessCodeSystem,
35+
seriousnessCodeSystem,
3036
seriousnessDisplayText,
31-
categoryCode: category,
32-
categoryCodeSystem: !categoryCodeSystem ? 'http://terminology.hl7.org/CodeSystem/adverse-event-category' : categoryCodeSystem,
33-
categoryDisplayText,
37+
category: categoryCodes.map((categoryCode, index) => {
38+
if (!categoryCode) return null;
39+
const categoryCoding = { code: categoryCode, system: categorySystems[index] ? categorySystems[index] : 'http://terminology.hl7.org/CodeSystem/adverse-event-category' };
40+
if (categoryDisplays[index]) categoryCoding.display = categoryDisplays[index];
41+
return categoryCoding;
42+
}),
3443
severity,
3544
actuality: !actuality ? 'actual' : actuality,
3645
studyId,

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/templates/AdverseEventTemplate.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const { coding, reference } = require('./snippets');
2-
const { ifAllArgsObj, ifSomeArgsObj, ifAllArgs } = require('../helpers/templateUtils');
2+
const { ifAllArgsObj, ifSomeArgsObj, ifAllArgs, ifSomeArgsArr } = require('../helpers/templateUtils');
33

44
function eventTemplate(eventCoding) {
55
return {
@@ -27,22 +27,24 @@ function seriousnessTemplate(seriousnessCoding) {
2727
return {
2828
seriousness: {
2929
coding: [
30-
coding(seriousnessCoding),
30+
coding({ system: 'http://terminology.hl7.org/CodeSystem/adverse-event-seriousness', ...seriousnessCoding }),
3131
],
3232
},
3333
};
3434
}
3535

36-
function categoryTemplate(categoryCoding) {
36+
function individualCategoryTemplate(category) {
3737
return {
38-
category: [{
39-
coding: [
40-
coding(categoryCoding),
41-
],
42-
}],
38+
coding: [coding(category),
39+
],
4340
};
4441
}
4542

43+
function categoryArrayTemplate(categoryArr) {
44+
const category = categoryArr.map(individualCategoryTemplate);
45+
return { category };
46+
}
47+
4648
function severityTemplate(severityCode) {
4749
return {
4850
severity: {
@@ -71,11 +73,11 @@ function recordedDateTemplate(recordedDateTime) {
7173
}
7274

7375
function adverseEventTemplate({
74-
id, subjectId, code, system, display, suspectedCauseId, suspectedCauseType, seriousnessCode, seriousnessCodeSystem, seriousnessDisplayText, categoryCode, categoryCodeSystem, categoryDisplayText,
76+
id, subjectId, code, system, display, suspectedCauseId, suspectedCauseType, seriousnessCode, seriousnessCodeSystem, seriousnessDisplayText, category,
7577
severity, actuality, studyId, effectiveDateTime, recordedDateTime,
7678
}) {
77-
if (!(subjectId && code && effectiveDateTime)) {
78-
throw Error('Trying to render an AdverseEventTemplate, but a required argument is messing; ensure that subjectId, code and effectiveDateTime are all present');
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');
7981
}
8082

8183
return {
@@ -85,7 +87,7 @@ function adverseEventTemplate({
8587
...ifSomeArgsObj(eventTemplate)({ code, system, display }),
8688
...ifAllArgsObj(suspectedCauseTemplate)({ suspectedCauseId, suspectedCauseType }),
8789
...ifSomeArgsObj(seriousnessTemplate)({ code: seriousnessCode, system: seriousnessCodeSystem, display: seriousnessDisplayText }),
88-
...ifSomeArgsObj(categoryTemplate)({ code: categoryCode, system: categoryCodeSystem, display: categoryDisplayText }),
90+
...ifSomeArgsArr(categoryArrayTemplate)(category),
8991
...ifAllArgs(severityTemplate)(severity),
9092
actuality,
9193
...ifAllArgs(studyTemplate)(studyId),
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const path = require('path');
2+
const rewire = require('rewire');
3+
const _ = require('lodash');
4+
const { CSVAdverseEventExtractor } = require('../../src/extractors');
5+
const exampleCSVAdverseEventModuleResponse = require('./fixtures/csv-adverse-event-module-response.json');
6+
const exampleCSVAdverseEventBundle = require('./fixtures/csv-adverse-event-bundle.json');
7+
8+
// Rewired extractor for helper tests
9+
const CSVAdverseEventExtractorRewired = rewire('../../src/extractors/CSVAdverseEventExtractor.js');
10+
11+
// Constants for tests
12+
const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response above
13+
const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error
14+
15+
// Instantiate module with parameters
16+
const csvAdverseEventExtractor = new CSVAdverseEventExtractor({
17+
filePath: MOCK_CSV_PATH,
18+
});
19+
20+
// Destructure all modules
21+
const { CSVModule } = csvAdverseEventExtractor;
22+
23+
// Spy on csvModule
24+
const csvModuleSpy = jest.spyOn(CSVModule, 'get');
25+
26+
const formatData = CSVAdverseEventExtractorRewired.__get__('formatData');
27+
28+
// Creating an example bundle with two medication statements
29+
const exampleEntry = exampleCSVAdverseEventModuleResponse[0];
30+
const expandedExampleBundle = _.cloneDeep(exampleCSVAdverseEventBundle);
31+
expandedExampleBundle.entry.push(exampleCSVAdverseEventBundle.entry[0]);
32+
33+
describe('CSVAdverseEventExtractor', () => {
34+
describe('formatData', () => {
35+
test('should join data appropriately and throw errors when missing required properties', () => {
36+
const expectedErrorString = 'The adverse event is missing an expected attribute. Adverse event code, mrn, and effective date are all required.';
37+
const localData = _.cloneDeep(exampleCSVAdverseEventModuleResponse);
38+
39+
// Test that valid maximal data works fine
40+
expect(formatData(exampleCSVAdverseEventModuleResponse)).toEqual(expect.anything());
41+
42+
// Test that deleting an optional value works fine
43+
delete localData[0].actuality;
44+
expect(formatData(exampleCSVAdverseEventModuleResponse)).toEqual(expect.anything());
45+
46+
// Test that deleting a mandatory value throws an error
47+
delete localData[0].mrn;
48+
expect(() => formatData(localData)).toThrow(new Error(expectedErrorString));
49+
});
50+
});
51+
52+
describe('get', () => {
53+
test('should return bundle with an AdverseEvent resource', async () => {
54+
csvModuleSpy.mockReturnValue(exampleCSVAdverseEventModuleResponse);
55+
const data = await csvAdverseEventExtractor.get({ mrn: MOCK_PATIENT_MRN });
56+
expect(data.resourceType).toEqual('Bundle');
57+
expect(data.type).toEqual('collection');
58+
expect(data.entry).toBeDefined();
59+
expect(data.entry.length).toEqual(1);
60+
expect(data.entry).toEqual(exampleCSVAdverseEventBundle.entry);
61+
});
62+
63+
test('should return empty bundle when no data available from module', async () => {
64+
csvModuleSpy.mockReturnValue([]);
65+
const data = await csvAdverseEventExtractor.get({ mrn: MOCK_PATIENT_MRN });
66+
expect(data.resourceType).toEqual('Bundle');
67+
expect(data.type).toEqual('collection');
68+
expect(data.entry).toBeDefined();
69+
expect(data.entry.length).toEqual(0);
70+
});
71+
72+
test('get() should return an array of 2 when two adverse event resources are tied to a single patient', async () => {
73+
exampleCSVAdverseEventModuleResponse.push(exampleEntry);
74+
csvModuleSpy.mockReturnValue(exampleCSVAdverseEventModuleResponse);
75+
const data = await csvAdverseEventExtractor.get({ mrn: MOCK_PATIENT_MRN });
76+
77+
expect(data.resourceType).toEqual('Bundle');
78+
expect(data.type).toEqual('collection');
79+
expect(data.entry).toBeDefined();
80+
expect(data.entry.length).toEqual(2);
81+
expect(data).toEqual(expandedExampleBundle);
82+
});
83+
});
84+
});
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"resourceType": "Bundle",
3+
"type": "collection",
4+
"entry": [
5+
{
6+
"fullUrl": "urn:uuid:adverseEventId-1",
7+
"resource": {
8+
"resourceType": "AdverseEvent",
9+
"id": "adverseEventId-1",
10+
"subject": {
11+
"reference": "urn:uuid:mrn-1",
12+
"type": "Patient"
13+
},
14+
"event": {
15+
"coding": [
16+
{
17+
"system": "code-system",
18+
"code": "109006",
19+
"display": "Anxiety disorder of childhood OR adolescence"
20+
}
21+
]
22+
},
23+
"suspectEntity": [
24+
{
25+
"instance": {
26+
"reference": "urn:uuid:procedure-id",
27+
"type": "Procedure"
28+
}
29+
}
30+
],
31+
"seriousness": {
32+
"coding": [
33+
{
34+
"system": "http://terminology.hl7.org/CodeSystem/adverse-event-seriousness",
35+
"code": "Serious",
36+
"display": "Serious"
37+
}
38+
]
39+
},
40+
"category": [
41+
{
42+
"coding": [
43+
{
44+
"system": "http://terminology.hl7.org/CodeSystem/adverse-event-category",
45+
"code": "product-use-error",
46+
"display": "Product Use Error"
47+
}
48+
]
49+
}
50+
],
51+
"severity": {
52+
"coding": [
53+
{
54+
"system": "http://terminology.hl7.org/CodeSystem/adverse-event-severity",
55+
"code": "severe"
56+
}
57+
]
58+
},
59+
"actuality": "actual",
60+
"study": [
61+
{
62+
"reference": "urn:uuid:researchId-1",
63+
"type": "ResearchStudy"
64+
}
65+
],
66+
"date": "1994-12-09",
67+
"recordedDate": "1994-12-09"
68+
}
69+
}
70+
]
71+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[
2+
{
3+
"mrn": "mrn-1",
4+
"adverseEventId": "adverseEventId-1",
5+
"adverseEventCode": "109006",
6+
"adverseEventCodeSystem": "code-system",
7+
"adverseEventDisplayText": "Anxiety disorder of childhood OR adolescence",
8+
"suspectedCauseId": "procedure-id",
9+
"suspectedCauseType": "Procedure",
10+
"seriousness": "Serious",
11+
"seriousnessCodeSystem": "http://terminology.hl7.org/CodeSystem/adverse-event-seriousness",
12+
"seriousnessDisplayText": "Serious",
13+
"category": "product-use-error",
14+
"categoryCodeSystem": "http://terminology.hl7.org/CodeSystem/adverse-event-category",
15+
"categoryDisplayText": "Product Use Error",
16+
"severity": "severe",
17+
"actuality": "actual",
18+
"studyId": "researchId-1",
19+
"effectiveDate": "12-09-1994",
20+
"recordedDate": "12-09-1994"
21+
}
22+
]

0 commit comments

Comments
 (0)