Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/CSV_Templates.xlsx
Binary file not shown.
8 changes: 4 additions & 4 deletions docs/ctc-adverse-event.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mrn,adverseEventId,adverseEventCode,adverseEventCodeSystem,adverseEventCodeVersion,adverseEventDisplayText,adverseEventText,suspectedCauseId,suspectedCauseType,seriousness,seriousnessCodeSystem,seriousnessDisplayText,category,categoryCodeSystem,categoryDisplayText,studyId,effectiveDate,recordedDate,grade
mrn-full-example,example-id-1,event-code,code-system,code-version,code-display,event-text,cause-id,resourceType,seriousness-code,code-system,seriousness-display,category-code,code-system,category-dislpay,id,1994-12-09,1994-12-09,1,
mrn-two-category-example,example-id-2,event-code,code-system,code-version,code-display,event-text,cause-id,resourceType,seriousness-code,code-system,seriousness-display,category-code|category-code,code-system|code-system,category-display|category-display,id,1994-12-09,1994-12-09,3
mrn-minimal-example,,code-from-default-system,,,,,,,,,,,,,,1994-12-09,,1
mrn,adverseEventId,adverseEventCode,adverseEventCodeSystem,adverseEventCodeVersion,adverseEventDisplayText,adverseEventText,suspectedCauseId,suspectedCauseType,seriousness,seriousnessCodeSystem,seriousnessDisplayText,category,categoryCodeSystem,categoryDisplayText,studyId,effectiveDate,recordedDate,grade,expectation,resolvedDate,seriousnessOutcome
mrn-full-example,example-id-1,event-code,code-system,code-version,code-display,event-text,cause-id,resourceType,seriousness-code,code-system,seriousness-display,category-code,code-system,category-dislpay,id,1994-12-09,1994-12-09,1,expectation-code,YYYY-MM-DD,seriousness-outcome-code
mrn-two-category-example,example-id-2,event-code,code-system,code-version,code-display,event-text,cause-id,resourceType,seriousness-code,code-system,seriousness-display,category-code,code-system,category-dislpay,id,1994-12-09,1994-12-09,1,expectation-code,YYYY-MM-DD,seriousness-outcome-code
mrn-minimal-example,,code-from-default-system,,,,,,,,,,,,,1994-12-09,,1,,,,,
25 changes: 24 additions & 1 deletion src/extractors/CSVCTCAdverseEventExtractor.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const path = require('path');
const { BaseCSVExtractor } = require('./BaseCSVExtractor');
const { generateMcodeResources } = require('../templates');
const { getEmptyBundle } = require('../helpers/fhirUtils');
const { getPatientFromContext } = require('../helpers/contextUtils');
const { formatDateTime } = require('../helpers/dateUtils');
const { getDisplayFromConcept } = require('../helpers/valueSetUtils');
const { ctcAEGradeCodeToTextLookup } = require('../helpers/lookups/ctcAdverseEventLookup');
const logger = require('../helpers/logger');

Expand All @@ -29,6 +31,9 @@ function formatData(adverseEventData, patientId) {
effectivedate: effectiveDate,
recordeddate: recordedDate,
grade,
expectation,
resolveddate: resolvedDate,
seriousnessoutcome: seriousnessOutcome,
} = data;

if (!(adverseEventCode && effectiveDate && grade)) {
Expand All @@ -43,7 +48,6 @@ function formatData(adverseEventData, patientId) {
throw new Error('A category attribute on the adverse event is missing a corresponding categoryCodeSystem or categoryDisplayText value.');
}


return {
...(adverseEventId && { id: adverseEventId }),
subjectId: patientId,
Expand All @@ -67,6 +71,25 @@ function formatData(adverseEventData, patientId) {
effectiveDateTime: formatDateTime(effectiveDate),
recordedDateTime: !recordedDate ? null : formatDateTime(recordedDate),
grade: { code: grade, display: ctcAEGradeCodeToTextLookup[grade] },
resolvedDate: !resolvedDate ? null : formatDateTime(resolvedDate),
expectation: !expectation ? null : {
code: expectation,
system: 'http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl',
display: getDisplayFromConcept(
path.resolve(__dirname, '..', 'helpers', 'valueSets', 'adverse-event-expectation-value-set.json'),
expectation,
'http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl',
),
},
seriousnessOutcome: !seriousnessOutcome ? null : {
code: seriousnessOutcome,
system: 'http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl',
display: getDisplayFromConcept(
path.resolve(__dirname, '..', 'helpers', 'valueSets', 'adverse-event-seriousness-outcome-value-set.json'),
seriousnessOutcome,
'http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl',
),
},
};
});
}
Expand Down
51 changes: 42 additions & 9 deletions src/helpers/valueSetUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,37 @@ function loadVs(absoluteFilepath, typeOfVS) {
}
}

function getConceptFromVSExpansion(valueSet, code, codeSystem) {
if (!code || !codeSystem || !valueSet) return undefined;
return valueSet.expansion.contains.find((containsItem) => containsItem
&& containsItem.system
&& code === containsItem.code
&& codeSystem === containsItem.system);
}

function getConceptFromVSCompose(valueSet, code, codeSystem) {
if (!code || !codeSystem || !valueSet) return undefined;
const includeItem = valueSet.compose.include.find((item) => item
&& item.system
&& item.system === codeSystem
&& item.concept);
if (!includeItem) return undefined;
return includeItem.concept.find((concept) => concept.code === code);
}

function getDisplayFromConcept(pathToValueSet, code, codeSystem) {
if (!code || !codeSystem || !pathToValueSet) return undefined;
const valueSet = loadVs(pathToValueSet, vsTypes.json);
let concept;
if (valueSet.expansion) {
// If valueSet has expansion, we only need to check these codes
concept = getConceptFromVSExpansion(valueSet, code, codeSystem);
return (concept && concept.display) ? concept.display : undefined;
}
concept = getConceptFromVSCompose(valueSet, code, codeSystem);
return (concept && concept.display) ? concept.display : undefined;
}

/**
* Check if code is in value set
* @param {string} code value to look for in a valueset
Expand All @@ -43,22 +74,21 @@ function loadVs(absoluteFilepath, typeOfVS) {
* @param {string} typeOfVS the file type of the value set to be searched
* @return {boolean} true if condition is in valueSet's compose block or expansion block
*/
const checkCodeInVs = (code, codeSystem, valueSetFilePath, typeOfVS = vsTypes.json) => {
const checkCodeInVs = (
code,
codeSystem,
valueSetFilePath,
typeOfVS = vsTypes.json,
) => {
const valueSet = loadVs(valueSetFilePath, typeOfVS);
let inVSExpansion = false;
let inVSCompose = false;
if (valueSet.expansion) {
// If valueSet has expansion, we only need to check these codes
inVSExpansion = valueSet.expansion.contains.some((containsItem) => {
if (!code || !codeSystem || !containsItem || !containsItem.system) return false;
return code === containsItem.code && codeSystem === containsItem.system;
});
inVSExpansion = (getConceptFromVSExpansion(valueSet, code, codeSystem) !== undefined);
} else {
// Checks if code is in any of the valueSet.compose.include arrays
inVSCompose = valueSet.compose.include.some((includeItem) => {
if (!code || !codeSystem || !includeItem || !includeItem.system || !includeItem.concept) return false;
return includeItem.system === codeSystem && includeItem.concept.map((concept) => concept.code).includes(code);
});
inVSCompose = (getConceptFromVSCompose(valueSet, code, codeSystem) !== undefined);
}
return inVSCompose || inVSExpansion;
};
Expand All @@ -68,4 +98,7 @@ module.exports = {
loadJsonVs,
loadVs,
checkCodeInVs,
getDisplayFromConcept,
getConceptFromVSCompose,
getConceptFromVSExpansion,
};
55 changes: 55 additions & 0 deletions src/helpers/valueSets/adverse-event-expectation-value-set.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"resourceType": "ValueSet",
"id": "adverse-event-expectation-value-set",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><ul><li>Include these codes as defined in <code>http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl</code><table class=\"none\"><tr><td style=\"white-space:nowrap\"><b>Code</b></td><td><b>Display</b></td></tr><tr><td>C41333</td><td>Expected Adverse Event</td></tr><tr><td>C41334</td><td>Unexpected Adverse Event</td></tr></table></li></ul></div>"
},
"url": "http://hl7.org/fhir/us/ctcae/ValueSet/adverse-event-expectation-value-set",
"version": "0.0.1",
"name": "AdverseEventExpectationVS",
"title": "Adverse Event Expectation Value Set",
"status": "active",
"date": "2021-12-03T16:46:43+00:00",
"publisher": "HL7 International Clinical Interoperability Council",
"contact": [
{
"name": "HL7 International Clinical Interoperability Council",
"telecom": [
{
"system": "url",
"value": "http://www.mcodeinitiative.org"
}
]
}
],
"description": "An expected adverse event is one whose nature and severity have been previously observed, identified in nature, severity, or frequency, and documented in the investigator brochure, investigational plan, protocol, current consent form, scientific publication, or in other relevant and reliable document. An unexpected adverse event is one that has not been previously observed, whether or not the event was anticipated because of the pharmacologic properties of the study agent or the nature of the medical procedure. This includes events that are more serious than expected or occur more frequently than expected, particularly, any adverse experience, the nature, severity or frequency of which is not consistent with the product label, or with the current investigator brochure for investigational agent; or with the risk information described in the investigational plan or protocol or consent form (NCI Thesaurus).",
"jurisdiction": [
{
"coding": [
{
"system": "urn:iso:std:iso:3166",
"code": "US",
"display": "United States of America"
}
]
}
],
"compose": {
"include": [
{
"system": "http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl",
"concept": [
{
"code": "C41333",
"display": "Expected Adverse Event"
},
{
"code": "C41334",
"display": "Unexpected Adverse Event"
}
]
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"resourceType": "ValueSet",
"id": "adverse-event-seriousness-outcome-value-set",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><ul><li>Include these codes as defined in <code>http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl</code><table class=\"none\"><tr><td style=\"white-space:nowrap\"><b>Code</b></td><td><b>Display</b></td></tr><tr><td>C84266</td><td>Life Threatening Adverse Event</td></tr><tr><td>C48275</td><td>Death Related to Adverse Event</td></tr><tr><td>C113380</td><td>Disabling Adverse Event</td></tr><tr><td>C83052</td><td>Adverse Event associated with Hospitalization</td></tr><tr><td>C2849</td><td>Congenital Abnormality</td></tr><tr><td>C52668</td><td>Intervention Required</td></tr></table></li></ul></div>"
},
"url": "http://hl7.org/fhir/us/ctcae/ValueSet/adverse-event-seriousness-outcome-value-set",
"version": "0.0.1",
"name": "AdverseEventSeriousnessOutcomeVS",
"title": "Adverse Event Seriousness Outcome Value Set",
"status": "active",
"date": "2021-12-03T16:46:43+00:00",
"publisher": "HL7 International Clinical Interoperability Council",
"contact": [
{
"name": "HL7 International Clinical Interoperability Council",
"telecom": [
{
"system": "url",
"value": "http://www.mcodeinitiative.org"
}
]
}
],
"description": "The outcome of a serious adverse event",
"jurisdiction": [
{
"coding": [
{
"system": "urn:iso:std:iso:3166",
"code": "US",
"display": "United States of America"
}
]
}
],
"compose": {
"include": [
{
"system": "http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl",
"concept": [
{
"code": "C84266",
"display": "Life Threatening Adverse Event"
},
{
"code": "C48275",
"display": "Death Related to Adverse Event"
},
{
"code": "C113380",
"display": "Disabling Adverse Event"
},
{
"code": "C83052",
"display": "Adverse Event associated with Hospitalization"
},
{
"code": "C2849",
"display": "Congenital Abnormality"
},
{
"code": "C52668",
"display": "Intervention Required"
}
]
}
]
}}
41 changes: 38 additions & 3 deletions src/templates/CTCAdverseEventTemplate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { coding, reference, extensionArr } = require('./snippets');
const { coding, reference, extensionArr, valueX } = require('./snippets');
const {
ifAllArgsObj, ifSomeArgsObj, ifAllArgs, ifSomeArgs, ifSomeArgsArr,
} = require('../helpers/templateUtils');
Expand Down Expand Up @@ -73,9 +73,39 @@ function gradeTemplate(grade) {
};
}

function resolvedDateTemplate(resolvedDate) {
return {
url: 'http://hl7.org/fhir/us/ctcae/StructureDefinition/adverse-event-resolved-date',
...valueX(resolvedDate, 'valueDateTime'),
};
}

function expectationTemplate(expectation) {
return {
url: 'http://hl7.org/fhir/us/ctcae/StructureDefinition/adverse-event-expectation',
valueCodeableConcept: {
coding: [
coding(expectation),
],
},
};
}

function seriousnessOutcomeTemplate(seriousnessOutcome) {
return {
url: 'http://hl7.org/fhir/us/ctcae/StructureDefinition/adverse-event-seriousness-outcome',
valueCodeableConcept: {
coding: [
coding(seriousnessOutcome),
],
},
};
}


function CTCAdverseEventTemplate({
id, subjectId, code, system, version, display, text, suspectedCauseId, suspectedCauseType, seriousnessCode, seriousnessCodeSystem, seriousnessDisplayText, category,
studyId, effectiveDateTime, recordedDateTime, grade,
studyId, effectiveDateTime, recordedDateTime, grade, resolvedDate, expectation, seriousnessOutcome,
}) {
if (!(subjectId && code && system && effectiveDateTime && grade)) {
throw Error('Trying to render an AdverseEventTemplate, but a required argument is messing; ensure that subjectId, code, system, grade, and effectiveDateTime are all present');
Expand All @@ -84,7 +114,12 @@ function CTCAdverseEventTemplate({
return {
resourceType: 'AdverseEvent',
id,
...extensionArr(gradeTemplate(grade)),
...extensionArr(
gradeTemplate(grade),
resolvedDate ? resolvedDateTemplate(resolvedDate) : null,
expectation ? expectationTemplate(expectation) : null,
seriousnessOutcome ? seriousnessOutcomeTemplate(seriousnessOutcome) : null,
),
subject: reference({ id: subjectId, resourceType: 'Patient' }),
...ifSomeArgs(eventTemplate)({ code, system, version, display }, text),
...ifAllArgsObj(suspectedCauseTemplate)({ suspectedCauseId, suspectedCauseType }),
Expand Down
28 changes: 28 additions & 0 deletions test/extractors/fixtures/csv-ctc-adverse-event-bundle.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@
}
]
}
},
{
"url": "http://hl7.org/fhir/us/ctcae/StructureDefinition/adverse-event-resolved-date",
"valueDateTime": "2021-12-01"
},
{
"url": "http://hl7.org/fhir/us/ctcae/StructureDefinition/adverse-event-expectation",
"valueCodeableConcept": {
"coding": [
{
"system": "http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl",
"code": "C41333",
"display": "Expected Adverse Event"
}
]
}
},
{
"url": "http://hl7.org/fhir/us/ctcae/StructureDefinition/adverse-event-seriousness-outcome",
"valueCodeableConcept": {
"coding": [
{
"system": "http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl",
"code": "C113380",
"display": "Disabling Adverse Event"
}
]
}
}
],
"subject": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"studyid": "researchId-1",
"effectivedate": "12-09-1994",
"recordeddate": "12-09-1994",
"grade": "1"
"grade": "1",
"resolveddate": "2021-12-01",
"seriousnessoutcome": "C113380",
"expectation": "C41333"
}
]
Loading