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
4 changes: 4 additions & 0 deletions docs/ctc-adverse-event.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mrn,adverseEventId,adverseEventCode,adverseEventCodeSystem,adverseEventDisplayText,suspectedCauseId,suspectedCauseType,seriousness,seriousnessCodeSystem,seriousnessDisplayText,category,categoryCodeSystem,categoryDisplayText,severity,actuality,studyId,effectiveDate,recordedDate
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
mrn-two-category-example,example-id-2,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
mrn-minimal-example,,code-from-default-system,,,,,,,,,,,,,,1994-12-09,
3 changes: 3 additions & 0 deletions src/client/MCODEClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
CSVCancerRelatedMedicationRequestExtractor,
CSVClinicalTrialInformationExtractor,
CSVConditionExtractor,
CSVCTCAdverseEventExtractor,
CSVObservationExtractor,
CSVPatientExtractor,
CSVProcedureExtractor,
Expand Down Expand Up @@ -35,6 +36,7 @@ class MCODEClient extends BaseClient {
CSVCancerRelatedMedicationRequestExtractor,
CSVClinicalTrialInformationExtractor,
CSVConditionExtractor,
CSVCTCAdverseEventExtractor,
CSVObservationExtractor,
CSVPatientExtractor,
CSVProcedureExtractor,
Expand Down Expand Up @@ -67,6 +69,7 @@ class MCODEClient extends BaseClient {
{ type: 'CSVProcedureExtractor', dependencies: ['CSVPatientExtractor'] },
{ type: 'CSVObservationExtractor', dependencies: ['CSVPatientExtractor'] },
{ type: 'CSVAdverseEventExtractor', dependencies: ['CSVPatientExtractor'] },
{ type: 'CSVCTCAdverseEventExtractor', dependencies: ['CSVPatientExtractor'] },
];
// Sort extractors based on order and dependencies
this.extractorConfig = sortExtractors(this.extractorConfig, dependencyInfo);
Expand Down
99 changes: 99 additions & 0 deletions src/extractors/CSVCTCAdverseEventExtractor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const { BaseCSVExtractor } = require('./BaseCSVExtractor');
const { generateMcodeResources } = require('../templates');
const { getEmptyBundle } = require('../helpers/fhirUtils');
const { getPatientFromContext } = require('../helpers/contextUtils');
const { formatDateTime } = require('../helpers/dateUtils');
const logger = require('../helpers/logger');

// Formats data to be passed into template-friendly format
function formatData(adverseEventData, patientId) {
logger.debug('Reformatting adverse event data from CSV into template format');
return adverseEventData.map((data) => {
const {
adverseeventid: adverseEventId,
adverseeventcode: adverseEventCode,
adverseeventcodesystem: adverseEventCodeSystem,
adverseeventdisplaytext: adverseEventDisplayText,
suspectedcauseid: suspectedCauseId,
suspectedcausetype: suspectedCauseType,
seriousness,
seriousnesscodesystem: seriousnessCodeSystem,
seriousnessdisplaytext: seriousnessDisplayText,
category,
categorycodesystem: categoryCodeSystem,
categorydisplaytext: categoryDisplayText,
severity,
actuality,
studyid: studyId,
effectivedate: effectiveDate,
recordeddate: recordedDate,
} = data;

if (!(adverseEventCode && effectiveDate)) {
throw new Error('The adverse event is missing an expected attribute. Adverse event code and effective date are all required.');
}

const categoryCodes = category.split('|');
const categorySystems = categoryCodeSystem.split('|');
const categoryDisplays = categoryDisplayText.split('|');

if (!(categoryCodes.length === categorySystems.length && categoryCodes.length === categoryDisplays.length)) {
throw new Error('A category attribute on the adverse event is missing a corresponding categoryCodeSystem or categoryDisplayText value.');
}


return {
...(adverseEventId && { id: adverseEventId }),
subjectId: patientId,
code: adverseEventCode,
system: !adverseEventCodeSystem ? 'http://snomed.info/sct' : adverseEventCodeSystem,
display: adverseEventDisplayText,
suspectedCauseId,
suspectedCauseType,
seriousnessCode: seriousness,
seriousnessCodeSystem: !seriousnessCodeSystem ? 'http://terminology.hl7.org/CodeSystem/adverse-event-seriousness' : seriousnessCodeSystem,
seriousnessDisplayText,
category: categoryCodes.map((categoryCode, index) => {
if (!categoryCode) return null;
const categoryCoding = { code: categoryCode, system: categorySystems[index] ? categorySystems[index] : 'http://terminology.hl7.org/CodeSystem/adverse-event-category' };
if (categoryDisplays[index]) categoryCoding.display = categoryDisplays[index];
return categoryCoding;
}),
severity,
actuality: !actuality ? 'actual' : actuality,
studyId,
effectiveDateTime: formatDateTime(effectiveDate),
recordedDateTime: !recordedDate ? null : formatDateTime(recordedDate),
};
});
}

class CSVCTCAdverseEventExtractor extends BaseCSVExtractor {
constructor({ filePath, url }) {
super({ filePath, url });
}

async getAdverseEventData(mrn) {
logger.debug('Getting Adverse Event Data');
return this.csvModule.get('mrn', mrn);
}

async get({ mrn, context }) {
const adverseEventData = await this.getAdverseEventData(mrn);
if (adverseEventData.length === 0) {
logger.warn('No adverse event data found for patient');
return getEmptyBundle();
}
const patientId = getPatientFromContext(context).id;

// Reformat data
const formattedData = formatData(adverseEventData, patientId);

// Fill templates
return generateMcodeResources('CTCAdverseEvent', formattedData);
}
}

module.exports = {
CSVCTCAdverseEventExtractor,
};
2 changes: 2 additions & 0 deletions src/extractors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { CSVCancerRelatedMedicationAdministrationExtractor } = require('./CSVCanc
const { CSVCancerRelatedMedicationRequestExtractor } = require('./CSVCancerRelatedMedicationRequestExtractor');
const { CSVClinicalTrialInformationExtractor } = require('./CSVClinicalTrialInformationExtractor');
const { CSVConditionExtractor } = require('./CSVConditionExtractor');
const { CSVCTCAdverseEventExtractor } = require('./CSVCTCAdverseEventExtractor');
const { CSVObservationExtractor } = require('./CSVObservationExtractor');
const { CSVPatientExtractor } = require('./CSVPatientExtractor');
const { CSVProcedureExtractor } = require('./CSVProcedureExtractor');
Expand Down Expand Up @@ -32,6 +33,7 @@ module.exports = {
CSVCancerRelatedMedicationRequestExtractor,
CSVClinicalTrialInformationExtractor,
CSVConditionExtractor,
CSVCTCAdverseEventExtractor,
CSVObservationExtractor,
CSVPatientExtractor,
CSVProcedureExtractor,
Expand Down
101 changes: 101 additions & 0 deletions src/templates/CTCAdverseEventTemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const { coding, reference } = require('./snippets');
const { ifAllArgsObj, ifSomeArgsObj, ifAllArgs, ifSomeArgsArr } = require('../helpers/templateUtils');

function eventTemplate(eventCoding) {
return {
event: {
coding: [
coding(eventCoding),
],
},
};
}

function suspectedCauseTemplate({ suspectedCauseId, suspectedCauseType }) {
return {
suspectEntity: [
{
instance:
reference({ id: suspectedCauseId, resourceType: suspectedCauseType }),
},
],
};
}


function seriousnessTemplate(seriousnessCoding) {
return {
seriousness: {
coding: [
coding(seriousnessCoding),
],
},
};
}

function individualCategoryTemplate(category) {
return {
coding: [coding(category),
],
};
}

function categoryArrayTemplate(categoryArr) {
const category = categoryArr.map(individualCategoryTemplate);
return { category };
}

function severityTemplate(severityCode) {
return {
severity: {
coding: [
coding({
code: severityCode,
system: 'http://terminology.hl7.org/CodeSystem/adverse-event-severity',
}),
],
},
};
}

function studyTemplate(studyId) {
return {
study: [
reference({ id: studyId, resourceType: 'ResearchStudy' }),
],
};
}

function recordedDateTemplate(recordedDateTime) {
return {
recordedDate: recordedDateTime,
};
}

function CTCAdverseEventTemplate({
id, subjectId, code, system, display, suspectedCauseId, suspectedCauseType, seriousnessCode, seriousnessCodeSystem, seriousnessDisplayText, category,
severity, actuality, studyId, effectiveDateTime, recordedDateTime,
}) {
if (!(subjectId && code && system && effectiveDateTime && actuality)) {
throw Error('Trying to render an AdverseEventTemplate, but a required argument is messing; ensure that subjectId, code, system, actuality, and effectiveDateTime are all present');
}

return {
resourceType: 'AdverseEvent',
id,
subject: reference({ id: subjectId, resourceType: 'Patient' }),
...ifSomeArgsObj(eventTemplate)({ code, system, display }),
...ifAllArgsObj(suspectedCauseTemplate)({ suspectedCauseId, suspectedCauseType }),
...ifSomeArgsObj(seriousnessTemplate)({ code: seriousnessCode, system: seriousnessCodeSystem, display: seriousnessDisplayText }),
...ifSomeArgsArr(categoryArrayTemplate)(category),
...ifAllArgs(severityTemplate)(severity),
actuality,
...ifAllArgs(studyTemplate)(studyId),
date: effectiveDateTime,
...ifAllArgs(recordedDateTemplate)(recordedDateTime),
};
}

module.exports = {
CTCAdverseEventTemplate,
};
2 changes: 2 additions & 0 deletions src/templates/ResourceGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { cancerRelatedMedicationAdministrationTemplate } = require('./CancerRelat
const { cancerRelatedMedicationRequestTemplate } = require('./CancerRelatedMedicationRequestTemplate');
const { carePlanWithReviewTemplate } = require('./CarePlanWithReviewTemplate');
const { conditionTemplate } = require('./ConditionTemplate');
const { CTCAdverseEventTemplate } = require('./CTCAdverseEventTemplate');
const { observationTemplate } = require('./ObservationTemplate');
const { patientTemplate } = require('./PatientTemplate');
const { procedureTemplate } = require('./ProcedureTemplate');
Expand All @@ -23,6 +24,7 @@ const fhirTemplateLookup = {
CancerRelatedMedicationRequest: cancerRelatedMedicationRequestTemplate,
CarePlanWithReview: carePlanWithReviewTemplate,
Condition: conditionTemplate,
CTCAdverseEvent: CTCAdverseEventTemplate,
Observation: observationTemplate,
Patient: patientTemplate,
Procedure: procedureTemplate,
Expand Down
4 changes: 4 additions & 0 deletions test/sample-client-data/ctc-adverse-event-information.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mrn,adverseEventId,adverseEventCode,adverseEventCodeSystem,adverseEventDisplayText,suspectedCauseId,suspectedCauseType,seriousness,seriousnessCodeSystem,seriousnessDisplayText,category,categoryCodeSystem,categoryDisplayText,severity,actuality,studyId,effectiveDate,recordedDate
123,adverseEventId-1,109006,code-system,Anxiety disorder of childhood OR adolescence,procedure-id,Procedure,serious,http://terminology.hl7.org/CodeSystem/adverse-event-seriousness,Serious,product-use-error|product-quality|wrong-rate,http://terminology.hl7.org/CodeSystem/adverse-event-category|http://snomed.info/sct|http://terminology.hl7.org/CodeSystem/adverse-event-category,Product Use Error|Product Quality|Wrong Rate,severe,actual,researchId-1,12-09-1994,12-09-1994
456,adverseEventId-2,134006,http://snomed.info/sct,Decreased hair growth,medicationId-1,Medication,non-serious,http://terminology.hl7.org/CodeSystem/adverse-event-seriousness,Non-serious,product-quality|wrong-rate,http://terminology.hl7.org/CodeSystem/adverse-event-category|,Product Quality|,mild,potential,researchId-2,12-10-1995,12-10-1995
789,adverseEventId-3,150003,,,,,,,,product-use-error,,,,,,12-09-1994,