From 68c5547f6be971314a64828ece7de7a02a92e01a Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Thu, 15 Apr 2021 13:53:31 -0400 Subject: [PATCH 01/10] All CSV extractors updated, adverseEvent tests fixed --- src/extractors/CSVAdverseEventExtractor.js | 26 +++++++++----- .../CSVCancerDiseaseStatusExtractor.js | 14 ++++---- .../CSVCancerRelatedMedicationExtractor.js | 24 +++++++++---- .../CSVClinicalTrialInformationExtractor.js | 24 ++++++------- src/extractors/CSVConditionExtractor.js | 26 +++++++++----- src/extractors/CSVObservationExtractor.js | 26 +++++++++----- src/extractors/CSVPatientExtractor.js | 2 +- src/extractors/CSVProcedureExtractor.js | 26 +++++++++----- src/extractors/CSVStagingExtractor.js | 26 +++++++++----- .../CSVTreatmentPlanChangeExtractor.js | 14 ++++---- .../CancerRelatedMedicationTemplate.js | 8 ++--- src/templates/CarePlanWithReviewTemplate.js | 8 ++--- .../CSVAdverseEventExtractor.test.js | 36 ++++++++++--------- .../extractors/FHIRConditionExtractor.test.js | 12 ++----- .../fixtures/context-with-patient.json | 12 +++++++ 15 files changed, 173 insertions(+), 111 deletions(-) create mode 100644 test/extractors/fixtures/context-with-patient.json diff --git a/src/extractors/CSVAdverseEventExtractor.js b/src/extractors/CSVAdverseEventExtractor.js index 09cfdd52..2f48fb61 100644 --- a/src/extractors/CSVAdverseEventExtractor.js +++ b/src/extractors/CSVAdverseEventExtractor.js @@ -1,19 +1,21 @@ const { BaseCSVExtractor } = require('./BaseCSVExtractor'); const { generateMcodeResources } = require('../templates'); -const logger = require('../helpers/logger'); +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) { +function formatData(adverseEventData, patientId) { logger.debug('Reformatting adverse event data from CSV into template format'); return adverseEventData.map((data) => { const { - mrn, adverseEventId, adverseEventCode, adverseEventCodeSystem, adverseEventDisplayText, suspectedCauseId, suspectedCauseType, seriousness, seriousnessCodeSystem, seriousnessDisplayText, + adverseEventId, adverseEventCode, adverseEventCodeSystem, adverseEventDisplayText, suspectedCauseId, suspectedCauseType, seriousness, seriousnessCodeSystem, seriousnessDisplayText, category, categoryCodeSystem, categoryDisplayText, severity, actuality, studyId, effectiveDate, recordedDate, } = data; - if (!(mrn && adverseEventCode && effectiveDate)) { - throw new Error('The adverse event is missing an expected attribute. Adverse event code, mrn, and effective date are all required.'); + 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('|'); @@ -27,7 +29,7 @@ function formatData(adverseEventData) { return { ...(adverseEventId && { id: adverseEventId }), - subjectId: mrn, + subjectId: patientId, code: adverseEventCode, system: !adverseEventCodeSystem ? 'http://snomed.info/sct' : adverseEventCodeSystem, display: adverseEventDisplayText, @@ -61,10 +63,18 @@ class CSVAdverseEventExtractor extends BaseCSVExtractor { return this.csvModule.get('mrn', mrn); } - async get({ mrn }) { + async get({ mrn, context }) { const adverseEventData = await this.getAdverseEventData(mrn); - const formattedData = formatData(adverseEventData); + 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('AdverseEvent', formattedData); } } diff --git a/src/extractors/CSVCancerDiseaseStatusExtractor.js b/src/extractors/CSVCancerDiseaseStatusExtractor.js index 69685241..166232ea 100644 --- a/src/extractors/CSVCancerDiseaseStatusExtractor.js +++ b/src/extractors/CSVCancerDiseaseStatusExtractor.js @@ -2,6 +2,7 @@ const { BaseCSVExtractor } = require('./BaseCSVExtractor'); const { formatDateTime } = require('../helpers/dateUtils'); const { getDiseaseStatusDisplay, getDiseaseStatusEvidenceDisplay } = require('../helpers/diseaseStatusUtils'); const { generateMcodeResources } = require('../templates'); +const { getPatientFromContext } = require('../helpers/contextUtils'); const { getEmptyBundle } = require('../helpers/fhirUtils'); const logger = require('../helpers/logger'); const { CSVCancerDiseaseStatusSchema } = require('../helpers/schemas/csv'); @@ -12,12 +13,12 @@ class CSVCancerDiseaseStatusExtractor extends BaseCSVExtractor { this.implementation = implementation; } - joinAndReformatData(arrOfDiseaseStatusData) { + joinAndReformatData(arrOfDiseaseStatusData, patientId) { logger.debug('Reformatting disease status data from CSV into template format'); // Check the shape of the data arrOfDiseaseStatusData.forEach((record) => { - if (!record.mrn || !record.conditionId || !record.diseaseStatusCode || !record.dateOfObservation) { - throw new Error('DiseaseStatusData missing an expected property: mrn, conditionId, diseaseStatusCode, and dateOfObservation are required.'); + if (!record.conditionId || !record.diseaseStatusCode || !record.dateOfObservation) { + throw new Error('DiseaseStatusData missing an expected property: conditionId, diseaseStatusCode, and dateOfObservation are required.'); } }); const evidenceDelimiter = '|'; @@ -29,7 +30,7 @@ class CSVCancerDiseaseStatusExtractor extends BaseCSVExtractor { display: record.diseaseStatusText ? record.diseaseStatusText : getDiseaseStatusDisplay(record.diseaseStatusCode, this.implementation), }, subject: { - id: record.mrn, + id: patientId, }, condition: { id: record.conditionId, @@ -47,16 +48,17 @@ class CSVCancerDiseaseStatusExtractor extends BaseCSVExtractor { return this.csvModule.get('mrn', mrn, fromDate, toDate); } - async get({ mrn, fromDate, toDate }) { + async get({ mrn, context, fromDate, toDate }) { // 1. Get all relevant data and do necessary post-processing const diseaseStatusData = await this.getDiseaseStatusData(mrn, fromDate, toDate); if (diseaseStatusData.length === 0) { logger.warn('No disease status data found for patient'); return getEmptyBundle(); } + const patientId = getPatientFromContext(context).id; // 2. Format data for research study and research subject - const packagedDiseaseStatusData = this.joinAndReformatData(diseaseStatusData); + const packagedDiseaseStatusData = this.joinAndReformatData(diseaseStatusData, patientId); // 3. Generate FHIR Resources const resources = generateMcodeResources('CancerDiseaseStatus', packagedDiseaseStatusData); diff --git a/src/extractors/CSVCancerRelatedMedicationExtractor.js b/src/extractors/CSVCancerRelatedMedicationExtractor.js index 259f9075..5d9e1123 100644 --- a/src/extractors/CSVCancerRelatedMedicationExtractor.js +++ b/src/extractors/CSVCancerRelatedMedicationExtractor.js @@ -1,24 +1,26 @@ const { BaseCSVExtractor } = require('./BaseCSVExtractor'); const { generateMcodeResources } = require('../templates'); -const logger = require('../helpers/logger'); +const { getPatientFromContext } = require('../helpers/contextUtils'); +const { getEmptyBundle } = require('../helpers/fhirUtils'); const { formatDateTime } = require('../helpers/dateUtils'); +const logger = require('../helpers/logger'); -function formatData(medicationData) { +function formatData(medicationData, patientId) { logger.debug('Reformatting cancer-related medication data from CSV into template format'); return medicationData.map((medication) => { const { - mrn, medicationId, code, codeSystem, displayText, startDate, endDate, treatmentReasonCode, treatmentReasonCodeSystem, treatmentReasonDisplayText, treatmentIntent, status, + medicationId, code, codeSystem, displayText, startDate, endDate, treatmentReasonCode, treatmentReasonCodeSystem, treatmentReasonDisplayText, treatmentIntent, status, } = medication; - if (!(mrn && code && codeSystem && status)) { + if (!(code && codeSystem && status)) { throw new Error('The cancer-related medication is missing an expected element; mrn, code, code system, and status are all required values.'); } return { - mrn, ...(medicationId && { id: medicationId }), + subjectId: patientId, code, codeSystem, displayText, @@ -43,10 +45,18 @@ class CSVCancerRelatedMedicationExtractor extends BaseCSVExtractor { return this.csvModule.get('mrn', mrn); } - async get({ mrn }) { + async get({ mrn, context }) { const medicationData = await this.getMedicationData(mrn); - const formattedData = formatData(medicationData); + if (medicationData.length === 0) { + logger.warn('No medication data found for patient'); + return getEmptyBundle(); + } + const patientId = getPatientFromContext(context).id; + + // Reformat data + const formattedData = formatData(medicationData, patientId); + // Fill templates return generateMcodeResources('CancerRelatedMedication', formattedData); } } diff --git a/src/extractors/CSVClinicalTrialInformationExtractor.js b/src/extractors/CSVClinicalTrialInformationExtractor.js index 63331b8c..802c674a 100644 --- a/src/extractors/CSVClinicalTrialInformationExtractor.js +++ b/src/extractors/CSVClinicalTrialInformationExtractor.js @@ -1,19 +1,11 @@ +const _ = require('lodash'); const { BaseCSVExtractor } = require('./BaseCSVExtractor'); -const { firstEntryInBundle, getBundleResourcesByType } = require('../helpers/fhirUtils'); +const { firstEntryInBundle, getEmptyBundle } = require('../helpers/fhirUtils'); +const { getPatientFromContext } = require('../helpers/contextUtils'); const { generateMcodeResources } = require('../templates'); const logger = require('../helpers/logger'); const { CSVClinicalTrialInformationSchema } = require('../helpers/schemas/csv'); -function getPatientId(context) { - const patientInContext = getBundleResourcesByType(context, 'Patient', {}, true); - if (patientInContext) { - logger.debug('Patient resource found in context.'); - return patientInContext.id; - } - - logger.debug('No patient resource found in context.'); - return undefined; -} class CSVClinicalTrialInformationExtractor extends BaseCSVExtractor { constructor({ filePath, clinicalSiteID, clinicalSiteSystem }) { @@ -23,7 +15,7 @@ class CSVClinicalTrialInformationExtractor extends BaseCSVExtractor { this.clinicalSiteSystem = clinicalSiteSystem; } - joinClinicalTrialData(patientId, clinicalTrialData) { + joinClinicalTrialData(clinicalTrialData, patientId) { logger.debug('Reformatting clinical trial data from CSV into template format'); const { trialSubjectID, enrollmentStatus, trialResearchID, trialStatus, trialResearchSystem, @@ -61,11 +53,15 @@ class CSVClinicalTrialInformationExtractor extends BaseCSVExtractor { } async get({ mrn, context }) { - const patientId = getPatientId(context) || mrn; const clinicalTrialData = await this.getClinicalTrialData(mrn); + if (_.isEmpty(clinicalTrialData)) { + logger.warn('No clinicalTrial record found for patient'); + return getEmptyBundle(); + } + const patientId = getPatientFromContext(context).id; // Format data for research study and research subject - const formattedData = this.joinClinicalTrialData(patientId, clinicalTrialData); + const formattedData = this.joinClinicalTrialData(clinicalTrialData, patientId); const { formattedDataSubject, formattedDataStudy } = formattedData; // Generate ResearchSubject and ResearchStudy resources and combine into one bundle to return diff --git a/src/extractors/CSVConditionExtractor.js b/src/extractors/CSVConditionExtractor.js index 4249a3c0..694059ca 100644 --- a/src/extractors/CSVConditionExtractor.js +++ b/src/extractors/CSVConditionExtractor.js @@ -1,24 +1,26 @@ const { BaseCSVExtractor } = require('./BaseCSVExtractor'); const { generateMcodeResources } = require('../templates'); -const logger = require('../helpers/logger'); +const { getPatientFromContext } = require('../helpers/contextUtils'); +const { getEmptyBundle } = require('../helpers/fhirUtils'); const { formatDateTime } = require('../helpers/dateUtils'); const { CSVConditionSchema } = require('../helpers/schemas/csv'); +const logger = require('../helpers/logger'); // Formats data to be passed into template-friendly format -function formatData(conditionData) { +function formatData(conditionData, patientId) { logger.debug('Reformatting condition data from CSV into template format'); return conditionData.map((data) => { const { - mrn, conditionId, codeSystem, code, displayName, category, dateOfDiagnosis, clinicalStatus, verificationStatus, bodySite, laterality, histology, + conditionId, codeSystem, code, displayName, category, dateOfDiagnosis, clinicalStatus, verificationStatus, bodySite, laterality, histology, } = data; - if (!(conditionId && mrn && codeSystem && code && category)) { - throw new Error('The condition is missing an expected attribute. Condition id, mrn, code system, code, and category are all required.'); + if (!(conditionId && codeSystem && code && category)) { + throw new Error('The condition is missing an expected attribute. Condition id, code system, code, and category are all required.'); } return { id: conditionId, subject: { - id: mrn, + id: patientId, }, code: { code, @@ -46,10 +48,18 @@ class CSVConditionExtractor extends BaseCSVExtractor { return this.csvModule.get('mrn', mrn); } - async get({ mrn }) { + async get({ mrn, context }) { const conditionData = await this.getConditionData(mrn); - const formattedData = formatData(conditionData); + if (conditionData.length === 0) { + logger.warn('No condition data found for patient'); + return getEmptyBundle(); + } + const patientId = getPatientFromContext(context).id; + + // Reformat data + const formattedData = formatData(conditionData, patientId); + // Fill templates return generateMcodeResources('Condition', formattedData); } } diff --git a/src/extractors/CSVObservationExtractor.js b/src/extractors/CSVObservationExtractor.js index c27b8d19..8b13ff74 100644 --- a/src/extractors/CSVObservationExtractor.js +++ b/src/extractors/CSVObservationExtractor.js @@ -1,22 +1,24 @@ const { BaseCSVExtractor } = require('./BaseCSVExtractor'); const { generateMcodeResources } = require('../templates'); -const logger = require('../helpers/logger'); +const { getPatientFromContext } = require('../helpers/contextUtils'); +const { getEmptyBundle } = require('../helpers/fhirUtils'); const { formatDateTime } = require('../helpers/dateUtils'); +const logger = require('../helpers/logger'); -function formatData(observationData) { +function formatData(observationData, patientId) { logger.debug('Reformatting observation data from CSV into template format'); return observationData.map((data) => { const { - mrn, observationId, status, code, codeSystem, displayName, value, valueCodeSystem, effectiveDate, bodySite, laterality, + observationId, status, code, codeSystem, displayName, value, valueCodeSystem, effectiveDate, bodySite, laterality, } = data; - if (!mrn || !observationId || !status || !code || !codeSystem || !value || !effectiveDate) { - throw new Error('The observation is missing an expected attribute. Observation id, mrn, status, code, code system, value, and effective date are all required.'); + if (!observationId || !status || !code || !codeSystem || !value || !effectiveDate) { + throw new Error('The observation is missing an expected attribute. Observation id, status, code, code system, value, and effective date are all required.'); } return { id: observationId, - subjectId: mrn, + subjectId: patientId, status, code, system: codeSystem, @@ -40,10 +42,18 @@ class CSVObservationExtractor extends BaseCSVExtractor { return this.csvModule.get('mrn', mrn); } - async get({ mrn }) { + async get({ mrn, context }) { const observationData = await this.getObservationData(mrn); - const formattedData = formatData(observationData); + if (observationData.length === 0) { + logger.warn('No observation data found for patient'); + return getEmptyBundle(); + } + const patientId = getPatientFromContext(context).id; + + // Reformat data + const formattedData = formatData(observationData, patientId); + // Fill template return generateMcodeResources('Observation', formattedData); } } diff --git a/src/extractors/CSVPatientExtractor.js b/src/extractors/CSVPatientExtractor.js index 4ec9eba0..018b0a27 100644 --- a/src/extractors/CSVPatientExtractor.js +++ b/src/extractors/CSVPatientExtractor.js @@ -6,8 +6,8 @@ const { getEthnicityDisplay, getRaceDisplay, maskPatientData } = require('../helpers/patientUtils'); const { getEmptyBundle } = require('../helpers/fhirUtils'); -const logger = require('../helpers/logger'); const { CSVPatientSchema } = require('../helpers/schemas/csv'); +const logger = require('../helpers/logger'); function joinAndReformatData(patientData) { logger.debug('Reformatting patient data from CSV into template format'); diff --git a/src/extractors/CSVProcedureExtractor.js b/src/extractors/CSVProcedureExtractor.js index ae3f2116..ab6909d4 100644 --- a/src/extractors/CSVProcedureExtractor.js +++ b/src/extractors/CSVProcedureExtractor.js @@ -1,22 +1,24 @@ const { BaseCSVExtractor } = require('./BaseCSVExtractor'); const { generateMcodeResources } = require('../templates'); -const logger = require('../helpers/logger'); +const { getPatientFromContext } = require('../helpers/contextUtils'); +const { getEmptyBundle } = require('../helpers/fhirUtils'); const { formatDateTime } = require('../helpers/dateUtils'); +const logger = require('../helpers/logger'); // Formats data to be passed into template-friendly format -function formatData(procedureData) { +function formatData(procedureData, patientId) { logger.debug('Reformatting procedure data from CSV into template format'); return procedureData.map((data) => { const { - mrn, procedureId, conditionId, status, code, codeSystem, displayName, reasonCode, reasonCodeSystem, reasonDisplayName, bodySite, laterality, effectiveDate, treatmentIntent, + procedureId, conditionId, status, code, codeSystem, displayName, reasonCode, reasonCodeSystem, reasonDisplayName, bodySite, laterality, effectiveDate, treatmentIntent, } = data; - if (!(mrn && procedureId && status && code && codeSystem && effectiveDate)) { - throw new Error('The procedure is missing an expected attribute. Procedure id, mrn, code system, code, status and effective date are all required.'); + if (!(procedureId && status && code && codeSystem && effectiveDate)) { + throw new Error('The procedure is missing an expected attribute. Procedure id, code system, code, status and effective date are all required.'); } return { id: procedureId, - subjectId: mrn, + subjectId: patientId, status, code, system: codeSystem, @@ -43,10 +45,18 @@ class CSVProcedureExtractor extends BaseCSVExtractor { return this.csvModule.get('mrn', mrn); } - async get({ mrn }) { + async get({ mrn, context }) { const procedureData = await this.getProcedureData(mrn); - const formattedData = formatData(procedureData); + if (procedureData.length === 0) { + logger.warn('No procedure data found for patient'); + return getEmptyBundle(); + } + const patientId = getPatientFromContext(context).id; + + // Reformat data + const formattedData = formatData(procedureData, patientId); + // Fill templates return generateMcodeResources('Procedure', formattedData); } } diff --git a/src/extractors/CSVStagingExtractor.js b/src/extractors/CSVStagingExtractor.js index 5dd77c2d..ba3cc271 100644 --- a/src/extractors/CSVStagingExtractor.js +++ b/src/extractors/CSVStagingExtractor.js @@ -1,18 +1,19 @@ const { BaseCSVExtractor } = require('./BaseCSVExtractor'); -const { firstEntryInBundle } = require('../helpers/fhirUtils'); +const { firstEntryInBundle, getEmptyBundle } = require('../helpers/fhirUtils'); +const { getPatientFromContext } = require('../helpers/contextUtils'); const { generateMcodeResources } = require('../templates'); -const logger = require('../helpers/logger'); const { formatDateTime } = require('../helpers/dateUtils'); +const logger = require('../helpers/logger'); -function formatTNMCategoryData(stagingData) { +function formatTNMCategoryData(stagingData, patientId) { logger.debug('Reformatting TNM Category data into template format'); const formattedData = []; const { - mrn, conditionId, t, n, m, type, stagingSystem, stagingCodeSystem, effectiveDate, + conditionId, t, n, m, type, stagingSystem, stagingCodeSystem, effectiveDate, } = stagingData; - if (!mrn || !conditionId || !effectiveDate) { - throw new Error('Staging data is missing an expected property: mrn, conditionId, effectiveDate are required.'); + if (!conditionId || !effectiveDate) { + throw new Error('Staging data is missing an expected property: conditionId, effectiveDate are required.'); } // data needed for each TNM category @@ -22,7 +23,7 @@ function formatTNMCategoryData(stagingData) { stageType: type, stagingSystem, stagingCodeSystem, - subjectId: mrn, + subjectId: patientId, }; if (t) formattedData.push({ ...necessaryData, valueCode: t, categoryType: 'Tumor' }); @@ -59,11 +60,18 @@ class CSVStagingExtractor extends BaseCSVExtractor { return this.csvModule.get('mrn', mrn); } - async get({ mrn }) { + async get({ mrn, context }) { const stagingData = await this.getStagingData(mrn); + if (stagingData.length === 0) { + logger.warn('No Staging data found for patient'); + return getEmptyBundle(); + } + const patientId = getPatientFromContext(context).id; + + // Iterate over the loop of staging data rows and make resources for all of them const entryResources = []; stagingData.forEach((data) => { - const formattedCategoryData = formatTNMCategoryData(data); + const formattedCategoryData = formatTNMCategoryData(data, patientId); // Generate observation for each TNM category const mcodeCategoryResources = formattedCategoryData.map((d) => firstEntryInBundle(generateMcodeResources('TNMCategory', d))); diff --git a/src/extractors/CSVTreatmentPlanChangeExtractor.js b/src/extractors/CSVTreatmentPlanChangeExtractor.js index 0e1d3bef..cc21eb7a 100644 --- a/src/extractors/CSVTreatmentPlanChangeExtractor.js +++ b/src/extractors/CSVTreatmentPlanChangeExtractor.js @@ -7,7 +7,7 @@ const logger = require('../helpers/logger'); const { CSVTreatmentPlanChangeSchema } = require('../helpers/schemas/csv'); // Formats data to be passed into template-friendly format -function formatData(tpcData) { +function formatData(tpcData, patientId) { logger.debug('Reformatting treatment plan change data from CSV into template format'); // Nothing to format in empty array @@ -16,16 +16,14 @@ function formatData(tpcData) { } // Newly combined data has mrn and list of reviews to map to an extension - const combinedFormat = { mrn: tpcData[0].mrn, reviews: [] }; + const combinedFormat = { subjectId: patientId, reviews: [] }; // If there are multiple entries, combine them into one object with multiple reviews const combinedData = _.reduce(tpcData, (res, currentDataEntry) => { - const { - mrn, dateOfCarePlan, changed, reasonCode, reasonDisplayText, - } = currentDataEntry; + const { dateOfCarePlan, changed, reasonCode, reasonDisplayText } = currentDataEntry; - if (!mrn || !dateOfCarePlan || !changed) { - throw new Error('Treatment Plan Change Data missing an expected property: mrn, dateOfCarePlan, changed are required'); + if (!dateOfCarePlan || !changed) { + throw new Error('Treatment Plan Change Data missing an expected property: dateOfCarePlan, changed are required'); } // reasonCode is required if changed flag is true @@ -84,8 +82,10 @@ class CSVTreatmentPlanChangeExtractor extends BaseCSVExtractor { return getEmptyBundle(); } + // Reformat data const formattedData = formatData(tpcData); + // Fill templates return generateMcodeResources('CarePlanWithReview', formattedData); } } diff --git a/src/templates/CancerRelatedMedicationTemplate.js b/src/templates/CancerRelatedMedicationTemplate.js index fc9de10e..f29abf59 100644 --- a/src/templates/CancerRelatedMedicationTemplate.js +++ b/src/templates/CancerRelatedMedicationTemplate.js @@ -58,7 +58,7 @@ function treatmentReasonTemplate({ treatmentReasonCode, treatmentReasonCodeSyste function cancerRelatedMedicationTemplate({ - mrn, + subjectId, id, code, codeSystem, @@ -71,8 +71,8 @@ function cancerRelatedMedicationTemplate({ treatmentIntent, status, }) { - if (!(mrn && code && codeSystem && status)) { - throw Error('Trying to render a CancerRelatedMedicationTemplate, but a required argument is missing; ensure that mrn, code, code system, and status are all present'); + if (!(subjectId && code && codeSystem && status)) { + throw Error('Trying to render a CancerRelatedMedicationTemplate, but a required argument is missing; ensure that subjectId, code, code system, and status are all present'); } return { @@ -86,7 +86,7 @@ function cancerRelatedMedicationTemplate({ ...extensionArr(ifAllArgsObj(treatmentIntentTemplate)({ treatmentIntent })), status, ...medicationTemplate({ code, codeSystem, displayText }), - ...ifAllArgsObj(subjectTemplate)({ id: mrn }), + ...ifAllArgsObj(subjectTemplate)({ id: subjectId }), ...periodTemplate({ startDate, endDate }), ...ifAllArgsObj(treatmentReasonTemplate)({ treatmentReasonCode, treatmentReasonCodeSystem, treatmentReasonDisplayText }), }; diff --git a/src/templates/CarePlanWithReviewTemplate.js b/src/templates/CarePlanWithReviewTemplate.js index 5ac7b9e0..653f11c6 100644 --- a/src/templates/CarePlanWithReviewTemplate.js +++ b/src/templates/CarePlanWithReviewTemplate.js @@ -82,10 +82,10 @@ function categoryTemplate() { // Treatment Plan Change modeled with CarePlanWithReview Template // Uses the ICARE R4 Care Plan profile which is not published yet // For reference, ICARE R4 Care Plan profile: http://standardhealthrecord.org/guides/icare/StructureDefinition-icare-care-plan-with-review.html -function carePlanWithReviewTemplate({ id, mrn, name, reviews }) { - if (!(id && mrn && reviews)) { +function carePlanWithReviewTemplate({ id, subjectId, name, reviews }) { + if (!(id && subjectId && reviews)) { const errorMessage = 'Trying to render a CarePlanWithReviewTemplate, but a required argument was missing; ' - + 'ensure that id, mrn, reviews are all present'; + + 'ensure that id, subjectId, reviews are all present'; throw new Error(errorMessage); } return { @@ -96,7 +96,7 @@ function carePlanWithReviewTemplate({ id, mrn, name, reviews }) { ...extensionArr( ...reviews.map(carePlanChangeReasonExtensionTemplate), ), - ...subjectTemplate({ id: mrn, name }), + ...subjectTemplate({ id: subjectId, name }), status: 'draft', intent: 'proposal', ...categoryTemplate(), diff --git a/test/extractors/CSVAdverseEventExtractor.test.js b/test/extractors/CSVAdverseEventExtractor.test.js index 4abc75e6..c36650bb 100644 --- a/test/extractors/CSVAdverseEventExtractor.test.js +++ b/test/extractors/CSVAdverseEventExtractor.test.js @@ -2,14 +2,13 @@ const path = require('path'); const rewire = require('rewire'); const _ = require('lodash'); const { CSVAdverseEventExtractor } = require('../../src/extractors'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); const exampleCSVAdverseEventModuleResponse = require('./fixtures/csv-adverse-event-module-response.json'); const exampleCSVAdverseEventBundle = require('./fixtures/csv-adverse-event-bundle.json'); - -// Rewired extractor for helper tests -const CSVAdverseEventExtractorRewired = rewire('../../src/extractors/CSVAdverseEventExtractor.js'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Constants for tests -const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response above +const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response and context-with-patient above const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error // Instantiate module with parameters @@ -23,51 +22,54 @@ const { csvModule } = csvAdverseEventExtractor; // Spy on csvModule const csvModuleSpy = jest.spyOn(csvModule, 'get'); -const formatData = CSVAdverseEventExtractorRewired.__get__('formatData'); - // Creating an example bundle with two medication statements const exampleEntry = exampleCSVAdverseEventModuleResponse[0]; const expandedExampleBundle = _.cloneDeep(exampleCSVAdverseEventBundle); expandedExampleBundle.entry.push(exampleCSVAdverseEventBundle.entry[0]); +// Rewired extractor for helper tests +const CSVAdverseEventExtractorRewired = rewire('../../src/extractors/CSVAdverseEventExtractor.js'); + describe('CSVAdverseEventExtractor', () => { describe('formatData', () => { + const formatData = CSVAdverseEventExtractorRewired.__get__('formatData'); test('should join data appropriately and throw errors when missing required properties', () => { - const expectedErrorString = 'The adverse event is missing an expected attribute. Adverse event code, mrn, and effective date are all required.'; + const expectedErrorString = 'The adverse event is missing an expected attribute. Adverse event code and effective date are all required.'; const expectedCategoryErrorString = 'A category attribute on the adverse event is missing a corresponding categoryCodeSystem or categoryDisplayText value.'; const localData = _.cloneDeep(exampleCSVAdverseEventModuleResponse); + const patientId = getPatientFromContext(MOCK_CONTEXT).id; // Test that valid maximal data works fine - expect(formatData(exampleCSVAdverseEventModuleResponse)).toEqual(expect.anything()); + expect(formatData(exampleCSVAdverseEventModuleResponse, patientId)).toEqual(expect.anything()); // Test that deleting an optional value works fine delete localData[0].actuality; - expect(formatData(exampleCSVAdverseEventModuleResponse)).toEqual(expect.anything()); + expect(formatData(exampleCSVAdverseEventModuleResponse, patientId)).toEqual(expect.anything()); // Test that adding another category but not adding a corresponding categoryCodeSystem throws an error localData[0].category = 'product-use-error|product-problem'; - expect(() => formatData(localData)).toThrow(new Error(expectedCategoryErrorString)); + expect(() => formatData(localData, patientId)).toThrow(new Error(expectedCategoryErrorString)); // Test that adding another category but adding a corresponding categoryCodeSystem and categoryDisplayText works fine localData[0].categoryCodeSystem = 'http://terminology.hl7.org/CodeSystem/adverse-event-category|http://snomed.info/sct'; localData[0].categoryDisplayText = 'Product Use Error|Product Problem'; - expect(formatData(localData)).toEqual(expect.anything()); + expect(formatData(localData, patientId)).toEqual(expect.anything()); // Test that adding another category but including syntax for default categoryCodeSystem and categoryDisplayText values works fine localData[0].categoryCodeSystem = 'http://terminology.hl7.org/CodeSystem/adverse-event-category|'; localData[0].categoryDisplayText = 'Product Use Error|'; - expect(formatData(localData)).toEqual(expect.anything()); + expect(formatData(localData, patientId)).toEqual(expect.anything()); // Test that deleting a mandatory value throws an error - delete localData[0].mrn; - expect(() => formatData(localData)).toThrow(new Error(expectedErrorString)); + delete localData[0].adverseEventCode; + expect(() => formatData(localData, patientId)).toThrow(new Error(expectedErrorString)); }); }); describe('get', () => { test('should return bundle with an AdverseEvent resource', async () => { csvModuleSpy.mockReturnValue(exampleCSVAdverseEventModuleResponse); - const data = await csvAdverseEventExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvAdverseEventExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); @@ -77,7 +79,7 @@ describe('CSVAdverseEventExtractor', () => { test('should return empty bundle when no data available from module', async () => { csvModuleSpy.mockReturnValue([]); - const data = await csvAdverseEventExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvAdverseEventExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); @@ -87,7 +89,7 @@ describe('CSVAdverseEventExtractor', () => { test('get() should return an array of 2 when two adverse event resources are tied to a single patient', async () => { exampleCSVAdverseEventModuleResponse.push(exampleEntry); csvModuleSpy.mockReturnValue(exampleCSVAdverseEventModuleResponse); - const data = await csvAdverseEventExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvAdverseEventExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); diff --git a/test/extractors/FHIRConditionExtractor.test.js b/test/extractors/FHIRConditionExtractor.test.js index 91f92172..9da357a7 100644 --- a/test/extractors/FHIRConditionExtractor.test.js +++ b/test/extractors/FHIRConditionExtractor.test.js @@ -1,20 +1,12 @@ const rewire = require('rewire'); const { FHIRConditionExtractor } = require('../../src/extractors/FHIRConditionExtractor.js'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); const FHIRConditionExtractorRewired = rewire('../../src/extractors/FHIRConditionExtractor'); const MOCK_URL = 'http://example.com'; const MOCK_HEADERS = {}; -const MOCK_MRN = '123456789'; const MOCK_CATEGORIES = 'category1,category2'; -const MOCK_CONTEXT = { - resourceType: 'Bundle', - entry: [ - { - fullUrl: 'context-url', - resource: { resourceType: 'Patient', id: MOCK_MRN }, - }, - ], -}; + const extractor = new FHIRConditionExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS }); const extractorWithCategories = new FHIRConditionExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS, category: MOCK_CATEGORIES }); diff --git a/test/extractors/fixtures/context-with-patient.json b/test/extractors/fixtures/context-with-patient.json new file mode 100644 index 00000000..d486da9b --- /dev/null +++ b/test/extractors/fixtures/context-with-patient.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Bundle", + "entry": [ + { + "fullUrl": "context-url", + "resource": { + "resourceType": "Patient", + "id": "mrn-1" + } + } + ] +} From bf4ef147717e42462a1efac1263a12cb9a3aef37 Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Thu, 15 Apr 2021 13:56:38 -0400 Subject: [PATCH 02/10] Fixed CDS Extractor --- .../CSVCancerDiseaseStatusExtractor.test.js | 10 ++++---- .../csv-cancer-disease-status-bundle.json | 24 +++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/test/extractors/CSVCancerDiseaseStatusExtractor.test.js b/test/extractors/CSVCancerDiseaseStatusExtractor.test.js index 890cd8e1..9d6656cf 100644 --- a/test/extractors/CSVCancerDiseaseStatusExtractor.test.js +++ b/test/extractors/CSVCancerDiseaseStatusExtractor.test.js @@ -1,8 +1,10 @@ const path = require('path'); const _ = require('lodash'); const { CSVCancerDiseaseStatusExtractor } = require('../../src/extractors'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); const exampleCSVDiseaseStatusModuleResponse = require('./fixtures/csv-cancer-disease-status-module-response.json'); const exampleCSVDiseaseStatusBundle = require('./fixtures/csv-cancer-disease-status-bundle.json'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Constants for tests const MOCK_PATIENT_MRN = 'pat-mrn-1'; // linked to values in example-module-response above @@ -25,7 +27,7 @@ const csvModuleSpy = jest.spyOn(csvModule, 'get'); describe('CSVCancerDiseaseStatusExtractor', () => { describe('joinAndReformatData', () => { test('should join data appropriately and throw errors when missing required properties', () => { - const expectedErrorString = 'DiseaseStatusData missing an expected property: mrn, conditionId, diseaseStatusCode, and dateOfObservation are required.'; + const expectedErrorString = 'DiseaseStatusData missing an expected property: conditionId, diseaseStatusCode, and dateOfObservation are required.'; const localData = _.cloneDeep(exampleCSVDiseaseStatusModuleResponse); // Test that valid data works fine expect(csvCancerDiseaseStatusExtractor.joinAndReformatData(exampleCSVDiseaseStatusModuleResponse)).toEqual(expect.anything()); @@ -36,7 +38,7 @@ describe('CSVCancerDiseaseStatusExtractor', () => { // Only including required properties is valid expect(csvCancerDiseaseStatusExtractor.joinAndReformatData(localData)).toEqual(expect.anything()); - const requiredProperties = ['mrn', 'conditionId', 'diseaseStatusCode', 'dateOfObservation']; + const requiredProperties = ['conditionId', 'diseaseStatusCode', 'dateOfObservation']; // Removing each required property should throw an error requiredProperties.forEach((key) => { @@ -50,7 +52,7 @@ describe('CSVCancerDiseaseStatusExtractor', () => { describe('get', () => { test('should return bundle with Observation', async () => { csvModuleSpy.mockReturnValue(exampleCSVDiseaseStatusModuleResponse); - const data = await csvCancerDiseaseStatusExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvCancerDiseaseStatusExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); @@ -60,7 +62,7 @@ describe('CSVCancerDiseaseStatusExtractor', () => { test('should return empty bundle when no data available from module', async () => { csvModuleSpy.mockReturnValue([]); - const data = await csvCancerDiseaseStatusExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvCancerDiseaseStatusExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); diff --git a/test/extractors/fixtures/csv-cancer-disease-status-bundle.json b/test/extractors/fixtures/csv-cancer-disease-status-bundle.json index fcde7d2f..7ce64901 100644 --- a/test/extractors/fixtures/csv-cancer-disease-status-bundle.json +++ b/test/extractors/fixtures/csv-cancer-disease-status-bundle.json @@ -3,10 +3,10 @@ "type": "collection", "entry": [ { - "fullUrl": "urn:uuid:d1d13594c371179797ce1a58884f714624d5b1782c4775f0fa303d78e943d64c", + "fullUrl": "urn:uuid:4b9e9b5a8db529782cd9e89b68c6a3fac408d2195f025691a3f2850cab18057f", "resource": { "resourceType": "Observation", - "id": "d1d13594c371179797ce1a58884f714624d5b1782c4775f0fa303d78e943d64c", + "id": "4b9e9b5a8db529782cd9e89b68c6a3fac408d2195f025691a3f2850cab18057f", "meta": { "profile": [ "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-cancer-disease-status" @@ -40,7 +40,7 @@ } ], "subject": { - "reference": "urn:uuid:pat-mrn-1", + "reference": "urn:uuid:mrn-1", "type": "Patient" }, "effectiveDateTime": "2019-12-02", @@ -53,13 +53,13 @@ } ] }, - "extension" : [ + "extension": [ { - "url" : "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-evidence-type", - "valueCodeableConcept" : { - "coding" : [ + "url": "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-evidence-type", + "valueCodeableConcept": { + "coding": [ { - "system" : "http://snomed.info/sct", + "system": "http://snomed.info/sct", "display": "imaging", "code": "363679005" } @@ -67,11 +67,11 @@ } }, { - "url" : "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-evidence-type", - "valueCodeableConcept" : { - "coding" : [ + "url": "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-evidence-type", + "valueCodeableConcept": { + "coding": [ { - "system" : "http://snomed.info/sct", + "system": "http://snomed.info/sct", "display": "pathology", "code": "252416005" } From c4ad9c64c6774d4da17f56f12e6f2ee6cc3f682a Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Thu, 15 Apr 2021 14:14:58 -0400 Subject: [PATCH 03/10] Done CancerRelatedMeds; minor fixes to CDS and AE --- test/extractors/CSVAdverseEventExtractor.test.js | 2 +- .../CSVCancerDiseaseStatusExtractor.test.js | 10 ++++++---- .../CSVCancerRelatedMedicationExtractor.test.js | 16 +++++++++------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/test/extractors/CSVAdverseEventExtractor.test.js b/test/extractors/CSVAdverseEventExtractor.test.js index c36650bb..a6161e85 100644 --- a/test/extractors/CSVAdverseEventExtractor.test.js +++ b/test/extractors/CSVAdverseEventExtractor.test.js @@ -2,9 +2,9 @@ const path = require('path'); const rewire = require('rewire'); const _ = require('lodash'); const { CSVAdverseEventExtractor } = require('../../src/extractors'); -const { getPatientFromContext } = require('../../src/helpers/contextUtils'); const exampleCSVAdverseEventModuleResponse = require('./fixtures/csv-adverse-event-module-response.json'); const exampleCSVAdverseEventBundle = require('./fixtures/csv-adverse-event-bundle.json'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Constants for tests diff --git a/test/extractors/CSVCancerDiseaseStatusExtractor.test.js b/test/extractors/CSVCancerDiseaseStatusExtractor.test.js index 9d6656cf..7a01dc5f 100644 --- a/test/extractors/CSVCancerDiseaseStatusExtractor.test.js +++ b/test/extractors/CSVCancerDiseaseStatusExtractor.test.js @@ -1,9 +1,9 @@ const path = require('path'); const _ = require('lodash'); const { CSVCancerDiseaseStatusExtractor } = require('../../src/extractors'); -const { getPatientFromContext } = require('../../src/helpers/contextUtils'); const exampleCSVDiseaseStatusModuleResponse = require('./fixtures/csv-cancer-disease-status-module-response.json'); const exampleCSVDiseaseStatusBundle = require('./fixtures/csv-cancer-disease-status-bundle.json'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Constants for tests @@ -29,14 +29,16 @@ describe('CSVCancerDiseaseStatusExtractor', () => { test('should join data appropriately and throw errors when missing required properties', () => { const expectedErrorString = 'DiseaseStatusData missing an expected property: conditionId, diseaseStatusCode, and dateOfObservation are required.'; const localData = _.cloneDeep(exampleCSVDiseaseStatusModuleResponse); + const patientId = getPatientFromContext(MOCK_CONTEXT).id; + // Test that valid data works fine - expect(csvCancerDiseaseStatusExtractor.joinAndReformatData(exampleCSVDiseaseStatusModuleResponse)).toEqual(expect.anything()); + expect(csvCancerDiseaseStatusExtractor.joinAndReformatData(exampleCSVDiseaseStatusModuleResponse, patientId)).toEqual(expect.anything()); localData[0].evidence = ''; // Evidence is not required and will not throw an error localData[0].observationStatus = ''; // Observation Status is not required and will not throw an error // Only including required properties is valid - expect(csvCancerDiseaseStatusExtractor.joinAndReformatData(localData)).toEqual(expect.anything()); + expect(csvCancerDiseaseStatusExtractor.joinAndReformatData(localData, patientId)).toEqual(expect.anything()); const requiredProperties = ['conditionId', 'diseaseStatusCode', 'dateOfObservation']; @@ -44,7 +46,7 @@ describe('CSVCancerDiseaseStatusExtractor', () => { requiredProperties.forEach((key) => { const clonedData = _.cloneDeep(localData); clonedData[0][key] = ''; - expect(() => csvCancerDiseaseStatusExtractor.joinAndReformatData(clonedData)).toThrow(new Error(expectedErrorString)); + expect(() => csvCancerDiseaseStatusExtractor.joinAndReformatData(clonedData, patientId)).toThrow(new Error(expectedErrorString)); }); }); }); diff --git a/test/extractors/CSVCancerRelatedMedicationExtractor.test.js b/test/extractors/CSVCancerRelatedMedicationExtractor.test.js index 500689a2..3e4ef964 100644 --- a/test/extractors/CSVCancerRelatedMedicationExtractor.test.js +++ b/test/extractors/CSVCancerRelatedMedicationExtractor.test.js @@ -4,6 +4,8 @@ const _ = require('lodash'); const { CSVCancerRelatedMedicationExtractor } = require('../../src/extractors'); const exampleCSVMedicationModuleResponse = require('./fixtures/csv-medication-module-response.json'); const exampleCSVMedicationBundle = require('./fixtures/csv-medication-bundle.json'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Rewired extractor for helper tests const CSVCancerRelatedMedicationExtractorRewired = rewire('../../src/extractors/CSVCancerRelatedMedicationExtractor.js'); @@ -35,24 +37,25 @@ describe('CSVCancerRelatedMedicationExtractor', () => { test('should join data appropriately and throw errors when missing required properties', () => { const expectedErrorString = 'The cancer-related medication is missing an expected element; mrn, code, code system, and status are all required values.'; const localData = _.cloneDeep(exampleCSVMedicationModuleResponse); + const patientId = getPatientFromContext(MOCK_CONTEXT).id; // Test that valid maximal data works fine - expect(formatData(exampleCSVMedicationModuleResponse)).toEqual(expect.anything()); + expect(formatData(localData, patientId)).toEqual(expect.anything()); // Test that deleting an optional value works fine delete localData[0].treatmentIntent; - expect(formatData(exampleCSVMedicationModuleResponse)).toEqual(expect.anything()); + expect(formatData(localData, patientId)).toEqual(expect.anything()); // Test that deleting a mandatory value throws an error delete localData[0].code; - expect(() => formatData(localData)).toThrow(new Error(expectedErrorString)); + expect(() => formatData(localData, patientId)).toThrow(new Error(expectedErrorString)); }); }); describe('get', () => { test('should return bundle with a CancerRelatedMedication', async () => { csvModuleSpy.mockReturnValue(exampleCSVMedicationModuleResponse); - const data = await csvCancerRelatedMedicationExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvCancerRelatedMedicationExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); @@ -62,7 +65,7 @@ describe('CSVCancerRelatedMedicationExtractor', () => { test('should return empty bundle when no data available from module', async () => { csvModuleSpy.mockReturnValue([]); - const data = await csvCancerRelatedMedicationExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvCancerRelatedMedicationExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); @@ -72,8 +75,7 @@ describe('CSVCancerRelatedMedicationExtractor', () => { test('get() should return an array of 2 when two medication statements are tied to a single patient', async () => { exampleCSVMedicationModuleResponse.push(exampleEntry); csvModuleSpy.mockReturnValue(exampleCSVMedicationModuleResponse); - const data = await csvCancerRelatedMedicationExtractor.get({ mrn: MOCK_PATIENT_MRN }); - + const data = await csvCancerRelatedMedicationExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); From 2a352486a419c8d94848389583cd97bc67bdae08 Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Thu, 15 Apr 2021 14:23:53 -0400 Subject: [PATCH 04/10] updates to CTI tests --- .../CSVClinicalTrialInformationExtractor.js | 4 +- ...VClinicalTrialInformationExtractor.test.js | 48 ++++--------------- ...csv-clinical-trial-information-bundle.json | 17 +++++-- 3 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/extractors/CSVClinicalTrialInformationExtractor.js b/src/extractors/CSVClinicalTrialInformationExtractor.js index 802c674a..71e0ebda 100644 --- a/src/extractors/CSVClinicalTrialInformationExtractor.js +++ b/src/extractors/CSVClinicalTrialInformationExtractor.js @@ -22,8 +22,8 @@ class CSVClinicalTrialInformationExtractor extends BaseCSVExtractor { } = clinicalTrialData; const { clinicalSiteID, clinicalSiteSystem } = this; - if (!(patientId && clinicalSiteID && trialSubjectID && enrollmentStatus && trialResearchID && trialStatus)) { - throw new Error('Clinical trial missing an expected property: patientId, clinicalSiteID, trialSubjectID, enrollmentStatus, trialResearchID, and trialStatus are required.'); + if (!(clinicalSiteID && trialSubjectID && enrollmentStatus && trialResearchID && trialStatus)) { + throw new Error('Clinical trial missing an expected property: clinicalSiteID, trialSubjectID, enrollmentStatus, trialResearchID, and trialStatus are required.'); } // Need separate data objects for ResearchSubject and ResearchStudy so that they get different resource ids diff --git a/test/extractors/CSVClinicalTrialInformationExtractor.test.js b/test/extractors/CSVClinicalTrialInformationExtractor.test.js index 0efd1f7d..1bea996c 100644 --- a/test/extractors/CSVClinicalTrialInformationExtractor.test.js +++ b/test/extractors/CSVClinicalTrialInformationExtractor.test.js @@ -1,9 +1,10 @@ const path = require('path'); -const rewire = require('rewire'); const _ = require('lodash'); const { CSVClinicalTrialInformationExtractor } = require('../../src/extractors'); const exampleClinicalTrialInformationResponse = require('./fixtures/csv-clinical-trial-information-module-response.json'); const exampleClinicalTrialInformationBundle = require('./fixtures/csv-clinical-trial-information-bundle.json'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Constants for mock tests const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error @@ -26,34 +27,30 @@ const csvModuleSpy = jest.spyOn(csvModule, 'get'); csvModuleSpy .mockReturnValue(exampleClinicalTrialInformationResponse); -const getPatientId = rewire('../../src/extractors/CSVClinicalTrialInformationExtractor.js').__get__('getPatientId'); - describe('CSVClinicalTrialInformationExtractor', () => { describe('joinClinicalTrialData', () => { test('should join clinical trial data appropriately and throw errors when missing required properties', () => { const firstClinicalTrialInfoResponse = exampleClinicalTrialInformationResponse[0]; // Each patient will only have one entry per clinical trial - const expectedErrorString = 'Clinical trial missing an expected property: patientId, clinicalSiteID, trialSubjectID, enrollmentStatus, trialResearchID, and trialStatus are required.'; + const expectedErrorString = 'Clinical trial missing an expected property: clinicalSiteID, trialSubjectID, enrollmentStatus, trialResearchID, and trialStatus are required.'; + const patientId = getPatientFromContext(MOCK_CONTEXT).id; // Test required properties in CSV throw error Object.keys(firstClinicalTrialInfoResponse).forEach((key) => { const clonedData = _.cloneDeep(firstClinicalTrialInfoResponse); - expect(csvClinicalTrialInformationExtractor.joinClinicalTrialData(MOCK_PATIENT_MRN, clonedData)).toEqual(expect.anything()); + expect(csvClinicalTrialInformationExtractor.joinClinicalTrialData(clonedData, patientId)).toEqual(expect.anything()); if (key === 'mrn') return; // MRN is not required from CSV if (key === 'trialResearchSystem') return; // trialResearchSystem is an optional field delete clonedData[key]; - expect(() => csvClinicalTrialInformationExtractor.joinClinicalTrialData(MOCK_PATIENT_MRN, clonedData)).toThrow(new Error(expectedErrorString)); + expect(() => csvClinicalTrialInformationExtractor.joinClinicalTrialData(clonedData, patientId)).toThrow(new Error(expectedErrorString)); }); - // patientId is required to be passed in - expect(() => csvClinicalTrialInformationExtractor.joinClinicalTrialData(undefined, firstClinicalTrialInfoResponse)).toThrow(new Error(expectedErrorString)); - // joinClinicalTrialData should return correct format - expect(csvClinicalTrialInformationExtractor.joinClinicalTrialData(MOCK_PATIENT_MRN, firstClinicalTrialInfoResponse)).toEqual({ + expect(csvClinicalTrialInformationExtractor.joinClinicalTrialData(firstClinicalTrialInfoResponse, patientId)).toEqual({ formattedDataSubject: { enrollmentStatus: firstClinicalTrialInfoResponse.enrollmentStatus, trialSubjectID: firstClinicalTrialInfoResponse.trialSubjectID, trialResearchID: firstClinicalTrialInfoResponse.trialResearchID, - patientId: MOCK_PATIENT_MRN, + patientId, trialResearchSystem: firstClinicalTrialInfoResponse.trialResearchSystem, }, formattedDataStudy: { @@ -67,36 +64,9 @@ describe('CSVClinicalTrialInformationExtractor', () => { }); }); - describe('getPatientId', () => { - test('should return patient id when patient resource in context', () => { - const contextPatient = { - resourceType: 'Patient', - id: 'context-patient-id', - }; - const contextBundle = { - resourceType: 'Bundle', - type: 'collection', - entry: [ - { - fullUrl: 'context-url', - resource: contextPatient, - }, - ], - }; - - const patientId = getPatientId(contextBundle); - expect(patientId).toEqual(contextPatient.id); - }); - - test('getPatientId should return undefined when no patient resource in context', () => { - const patientId = getPatientId({}); - expect(patientId).toBeUndefined(); - }); - }); - describe('get', () => { test('should return a bundle with the correct resources', async () => { - const data = await csvClinicalTrialInformationExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvClinicalTrialInformationExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); diff --git a/test/extractors/fixtures/csv-clinical-trial-information-bundle.json b/test/extractors/fixtures/csv-clinical-trial-information-bundle.json index fe1bec3f..f583d355 100644 --- a/test/extractors/fixtures/csv-clinical-trial-information-bundle.json +++ b/test/extractors/fixtures/csv-clinical-trial-information-bundle.json @@ -3,14 +3,16 @@ "type": "collection", "entry": [ { - "fullUrl": "urn:uuid:f8acc19f44696eea31532ac9d3b658f1fea317eba9455eddc78de0857220d362", + "fullUrl": "urn:uuid:a9235b06dfe8c24b40b938c7a1265ed3087cf0fa43ef0f11375effbb25ecca25", "resource": { "resourceType": "ResearchSubject", - "id": "f8acc19f44696eea31532ac9d3b658f1fea317eba9455eddc78de0857220d362", + "id": "a9235b06dfe8c24b40b938c7a1265ed3087cf0fa43ef0f11375effbb25ecca25", "identifier": [ { "system": "http://example.com/clinicaltrialsubjectids", - "type": { "text": "Clinical Trial Subject ID" }, + "type": { + "text": "Clinical Trial Subject ID" + }, "value": "example-subjectId" } ], @@ -21,7 +23,10 @@ "value": "example-researchId" } }, - "individual": { "reference": "urn:uuid:EXAMPLE-MRN", "type": "Patient" } + "individual": { + "reference": "urn:uuid:mrn-1", + "type": "Patient" + } } }, { @@ -42,7 +47,9 @@ "identifier": [ { "system": "example-system", - "type": { "text": "Clinical Trial Research ID" }, + "type": { + "text": "Clinical Trial Research ID" + }, "value": "example-researchId" } ] From 40a8646fba9cabc298326e7cff0ab3c88ba3e438 Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Thu, 15 Apr 2021 17:00:21 -0400 Subject: [PATCH 05/10] Consistency updates and comments; fix procedure, observation, condition tests --- .../CSVCancerDiseaseStatusExtractor.test.js | 2 +- ...SVCancerRelatedMedicationExtractor.test.js | 2 +- ...VClinicalTrialInformationExtractor.test.js | 6 +-- test/extractors/CSVConditionExtractor.test.js | 47 ++++++++++--------- .../CSVObservationExtractor.test.js | 15 +++--- test/extractors/CSVProcedureExtractor.test.js | 18 ++++--- ...cancer-disease-status-module-response.json | 2 +- ...cal-trial-information-module-response.json | 4 +- .../fixtures/csv-observation-bundle.json | 15 +++--- .../csv-observation-module-response.json | 4 +- 10 files changed, 62 insertions(+), 53 deletions(-) diff --git a/test/extractors/CSVCancerDiseaseStatusExtractor.test.js b/test/extractors/CSVCancerDiseaseStatusExtractor.test.js index 7a01dc5f..54d3d5f7 100644 --- a/test/extractors/CSVCancerDiseaseStatusExtractor.test.js +++ b/test/extractors/CSVCancerDiseaseStatusExtractor.test.js @@ -7,7 +7,7 @@ const { getPatientFromContext } = require('../../src/helpers/contextUtils'); const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Constants for tests -const MOCK_PATIENT_MRN = 'pat-mrn-1'; // linked to values in example-module-response above +const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response and context-with-patient above const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error const IMPLEMENTATION = 'mcode'; diff --git a/test/extractors/CSVCancerRelatedMedicationExtractor.test.js b/test/extractors/CSVCancerRelatedMedicationExtractor.test.js index 3e4ef964..2473ac67 100644 --- a/test/extractors/CSVCancerRelatedMedicationExtractor.test.js +++ b/test/extractors/CSVCancerRelatedMedicationExtractor.test.js @@ -11,7 +11,7 @@ const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); const CSVCancerRelatedMedicationExtractorRewired = rewire('../../src/extractors/CSVCancerRelatedMedicationExtractor.js'); // Constants for tests -const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response above +const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response and context-with-patient above const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error // Instantiate module with parameters diff --git a/test/extractors/CSVClinicalTrialInformationExtractor.test.js b/test/extractors/CSVClinicalTrialInformationExtractor.test.js index 1bea996c..073252d2 100644 --- a/test/extractors/CSVClinicalTrialInformationExtractor.test.js +++ b/test/extractors/CSVClinicalTrialInformationExtractor.test.js @@ -7,10 +7,10 @@ const { getPatientFromContext } = require('../../src/helpers/contextUtils'); const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Constants for mock tests +const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response and context-with-patient above const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error const MOCK_CLINICAL_SITE_ID = 'EXAMPLE-CLINICAL-SITE-ID'; const MOCK_CLINICAL_SITE_SYSTEM = 'EXAMPLE-CLINICAL-SITE-SYSTEM'; -const MOCK_PATIENT_MRN = 'EXAMPLE-MRN'; // Instantiate module with mock parameters const csvClinicalTrialInformationExtractor = new CSVClinicalTrialInformationExtractor({ @@ -38,8 +38,8 @@ describe('CSVClinicalTrialInformationExtractor', () => { Object.keys(firstClinicalTrialInfoResponse).forEach((key) => { const clonedData = _.cloneDeep(firstClinicalTrialInfoResponse); expect(csvClinicalTrialInformationExtractor.joinClinicalTrialData(clonedData, patientId)).toEqual(expect.anything()); - if (key === 'mrn') return; // MRN is not required from CSV - if (key === 'trialResearchSystem') return; // trialResearchSystem is an optional field + if (key === 'patientId') return; // MRN is optional + if (key === 'trialResearchSystem') return; // trialResearchSystem is optional delete clonedData[key]; expect(() => csvClinicalTrialInformationExtractor.joinClinicalTrialData(clonedData, patientId)).toThrow(new Error(expectedErrorString)); }); diff --git a/test/extractors/CSVConditionExtractor.test.js b/test/extractors/CSVConditionExtractor.test.js index d0232b5c..3e7ff71d 100644 --- a/test/extractors/CSVConditionExtractor.test.js +++ b/test/extractors/CSVConditionExtractor.test.js @@ -3,9 +3,10 @@ const _ = require('lodash'); const { CSVConditionExtractor } = require('../../src/extractors'); const exampleConditionResponse = require('./fixtures/csv-condition-module-response.json'); const exampleConditionBundle = require('./fixtures/csv-condition-bundle.json'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Constants for mock tests -const MOCK_PATIENT_MRN = 'mrn-1'; +const MOCK_PATIENT_MRN = 'mrn-1';// linked to values in example-module-response and context-with-patient above const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error const csvConditionExtractor = new CSVConditionExtractor({ filePath: MOCK_CSV_PATH, @@ -23,26 +24,28 @@ expandedExampleBundle.entry.push(exampleConditionBundle.entry[0]); describe('CSV Condition Extractor tests', () => { - test('get should return a fhir bundle when MRN is known', async () => { - csvModuleSpy.mockReturnValue(exampleConditionResponse); - const data = await csvConditionExtractor.get({ mrn: MOCK_PATIENT_MRN }); - - expect(data.resourceType).toEqual('Bundle'); - expect(data.type).toEqual('collection'); - expect(data.entry).toBeDefined(); - expect(data.entry.length).toEqual(1); - expect(data).toEqual(exampleConditionBundle); - }); - - test('get() should return an array of 2 when two conditions are tied to a single patient', async () => { - exampleConditionResponse.push(exampleEntry); - csvModuleSpy.mockReturnValue(exampleConditionResponse); - const data = await csvConditionExtractor.get({ mrn: MOCK_PATIENT_MRN }); - - expect(data.resourceType).toEqual('Bundle'); - expect(data.type).toEqual('collection'); - expect(data.entry).toBeDefined(); - expect(data.entry.length).toEqual(2); - expect(data).toEqual(expandedExampleBundle); + describe('get', () => { + test('get should return a fhir bundle when MRN is known', async () => { + csvModuleSpy.mockReturnValue(exampleConditionResponse); + const data = await csvConditionExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); + + expect(data.resourceType).toEqual('Bundle'); + expect(data.type).toEqual('collection'); + expect(data.entry).toBeDefined(); + expect(data.entry.length).toEqual(1); + expect(data).toEqual(exampleConditionBundle); + }); + + test('get() should return an array of 2 when two conditions are tied to a single patient', async () => { + exampleConditionResponse.push(exampleEntry); + csvModuleSpy.mockReturnValue(exampleConditionResponse); + const data = await csvConditionExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); + + expect(data.resourceType).toEqual('Bundle'); + expect(data.type).toEqual('collection'); + expect(data.entry).toBeDefined(); + expect(data.entry.length).toEqual(2); + expect(data).toEqual(expandedExampleBundle); + }); }); }); diff --git a/test/extractors/CSVObservationExtractor.test.js b/test/extractors/CSVObservationExtractor.test.js index 6f15dbdc..9922d400 100644 --- a/test/extractors/CSVObservationExtractor.test.js +++ b/test/extractors/CSVObservationExtractor.test.js @@ -4,12 +4,14 @@ const _ = require('lodash'); const { CSVObservationExtractor } = require('../../src/extractors'); const exampleCSVObservationModuleResponse = require('./fixtures/csv-observation-module-response.json'); const exampleCSVObservationBundle = require('./fixtures/csv-observation-bundle.json'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Rewired extractor for helper tests const CSVObservationExtractorRewired = rewire('../../src/extractors/CSVObservationExtractor.js'); // Constants for tests -const MOCK_PATIENT_MRN = 'pat-mrn-1'; // linked to values in example-module-response above +const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response and context-with-patient above const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error // Instantiate module with parameters @@ -28,15 +30,16 @@ const formatData = CSVObservationExtractorRewired.__get__('formatData'); describe('CSVObservationExtractor', () => { describe('formatData', () => { test('should join data appropriately and throw errors when missing required properties', () => { - const expectedErrorString = 'The observation is missing an expected attribute. Observation id, mrn, status, code, code system, value, and effective date are all required.'; + const expectedErrorString = 'The observation is missing an expected attribute. Observation id, status, code, code system, value, and effective date are all required.'; const localData = _.cloneDeep(exampleCSVObservationModuleResponse); + const patientId = getPatientFromContext(MOCK_CONTEXT).id; // Test that valid maximal data works fine - expect(formatData(exampleCSVObservationModuleResponse)).toEqual(expect.anything()); + expect(formatData(exampleCSVObservationModuleResponse, patientId)).toEqual(expect.anything()); // Test that deleting an optional value works fine delete localData[0].bodySite; - expect(formatData(exampleCSVObservationModuleResponse)).toEqual(expect.anything()); + expect(formatData(exampleCSVObservationModuleResponse, patientId)).toEqual(expect.anything()); // Test that deleting a mandatory value throws an error delete localData[0].status; @@ -47,7 +50,7 @@ describe('CSVObservationExtractor', () => { describe('get', () => { test('should return bundle with Observation', async () => { csvModuleSpy.mockReturnValue(exampleCSVObservationModuleResponse); - const data = await csvObservationExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvObservationExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); @@ -57,7 +60,7 @@ describe('CSVObservationExtractor', () => { test('should return empty bundle when no data available from module', async () => { csvModuleSpy.mockReturnValue([]); - const data = await csvObservationExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvObservationExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); diff --git a/test/extractors/CSVProcedureExtractor.test.js b/test/extractors/CSVProcedureExtractor.test.js index 084560b2..65592c39 100644 --- a/test/extractors/CSVProcedureExtractor.test.js +++ b/test/extractors/CSVProcedureExtractor.test.js @@ -4,12 +4,14 @@ const _ = require('lodash'); const { CSVProcedureExtractor } = require('../../src/extractors'); const exampleCSVProcedureModuleResponse = require('./fixtures/csv-procedure-module-response.json'); const exampleCSVProcedureBundle = require('./fixtures/csv-procedure-bundle.json'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Rewired extractor for helper tests const CSVProcedureExtractorRewired = rewire('../../src/extractors/CSVProcedureExtractor.js'); // Constants for tests -const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response above +const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response and patient-context above const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error // Instantiate module with parameters @@ -27,25 +29,27 @@ const formatData = CSVProcedureExtractorRewired.__get__('formatData'); describe('CSVProcedureExtractor', () => { describe('formatData', () => { test('should join data appropriately and throw errors when missing required properties', () => { - const expectedErrorString = 'The procedure is missing an expected attribute. Procedure id, mrn, code system, code, status and effective date are all required.'; + const expectedErrorString = 'The procedure is missing an expected attribute. Procedure id, code system, code, status and effective date are all required.'; const localData = _.cloneDeep(exampleCSVProcedureModuleResponse); + const patientId = getPatientFromContext(MOCK_CONTEXT).id; + // Test that valid data works fine - expect(formatData(localData)).toEqual(expect.anything()); + expect(formatData(localData, patientId)).toEqual(expect.anything()); // Test that removing an optional value works delete localData[0].bodySite; - expect(formatData(localData)).toEqual(expect.anything()); + expect(formatData(localData, patientId)).toEqual(expect.anything()); // Test that removing a required value throws delete localData[0].procedureId; - expect(() => formatData(localData)).toThrow(new Error(expectedErrorString)); + expect(() => formatData(localData, patientId)).toThrow(new Error(expectedErrorString)); }); }); describe('get', () => { test('should return bundle with Procedure', async () => { csvModuleSpy.mockReturnValue(exampleCSVProcedureModuleResponse); - const data = await csvProcedureExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvProcedureExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); @@ -55,7 +59,7 @@ describe('CSVProcedureExtractor', () => { test('should return empty bundle when no data available from module', async () => { csvModuleSpy.mockReturnValue([]); - const data = await csvProcedureExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvProcedureExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); diff --git a/test/extractors/fixtures/csv-cancer-disease-status-module-response.json b/test/extractors/fixtures/csv-cancer-disease-status-module-response.json index 67c3f854..bc5a09ad 100644 --- a/test/extractors/fixtures/csv-cancer-disease-status-module-response.json +++ b/test/extractors/fixtures/csv-cancer-disease-status-module-response.json @@ -1,6 +1,6 @@ [ { - "mrn": "pat-mrn-1", + "mrn": "mrn-1", "conditionId": "cond-1", "diseaseStatusCode": "268910001", "dateOfObservation": "2019-12-02", diff --git a/test/extractors/fixtures/csv-clinical-trial-information-module-response.json b/test/extractors/fixtures/csv-clinical-trial-information-module-response.json index 735f811c..92558480 100644 --- a/test/extractors/fixtures/csv-clinical-trial-information-module-response.json +++ b/test/extractors/fixtures/csv-clinical-trial-information-module-response.json @@ -1,10 +1,10 @@ [ { - "mrn": "EXAMPLE-MRN", + "patientId": "mrn-1", "trialSubjectID": "example-subjectId", "enrollmentStatus": "example-enrollment-status", "trialResearchID": "example-researchId", "trialStatus": "example-trialStatus", - "trialResearchSystem":"example-system" + "trialResearchSystem": "example-system" } ] diff --git a/test/extractors/fixtures/csv-observation-bundle.json b/test/extractors/fixtures/csv-observation-bundle.json index 6c7cebdf..b610dfb1 100644 --- a/test/extractors/fixtures/csv-observation-bundle.json +++ b/test/extractors/fixtures/csv-observation-bundle.json @@ -17,7 +17,7 @@ } ] } - ], + ], "code": { "coding": [ { @@ -26,11 +26,11 @@ } ] }, - "subject" : { - "reference": "urn:uuid:example-patient", + "subject": { + "reference": "urn:uuid:mrn-1", "type": "Patient" }, - "effectiveDateTime" : "2020-01-02", + "effectiveDateTime": "2020-01-02", "valueCodeableConcept": { "coding": [ { @@ -39,7 +39,7 @@ } ] }, - "bodySite": { + "bodySite": { "extension": [ { "url": "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-laterality", @@ -52,16 +52,15 @@ ] } } - ], + ], "coding": [ { "system": "http://snomed.info/sct", "code": "12345" } - ] + ] } } } ] } - diff --git a/test/extractors/fixtures/csv-observation-module-response.json b/test/extractors/fixtures/csv-observation-module-response.json index 8c64ad16..d05e251b 100644 --- a/test/extractors/fixtures/csv-observation-module-response.json +++ b/test/extractors/fixtures/csv-observation-module-response.json @@ -1,6 +1,6 @@ [ { - "mrn": "example-patient", + "mrn": "mrn-1", "observationId": "observation-id", "status": "final", "code": "1695-6", @@ -12,4 +12,4 @@ "bodySite": "12345", "laterality": "678910" } -] \ No newline at end of file +] From ff76b0b1f572aecc9573df6030a822a58d001b6d Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Thu, 15 Apr 2021 18:10:06 -0400 Subject: [PATCH 06/10] Fix all tests here --- .../CSVTreatmentPlanChangeExtractor.js | 8 ++-- test/cli/fixtures/example-patient.csv | 4 ++ test/cli/mcodeExtraction.test.js | 7 +++ test/extractors/CSVStagingExtractor.test.js | 17 ++++--- .../CSVTreatmentPlanChangeExtractor.test.js | 31 ++++++++----- .../fixtures/csv-staging-bundle.json | 30 ++++++------- .../fixtures/csv-staging-module-response.json | 2 +- .../csv-treatment-plan-change-bundle.json | 45 ++++++++++++++----- ...treatment-plan-change-module-response.json | 4 +- test/templates/carePlanWithReview.test.js | 10 ++--- test/templates/medication.test.js | 8 ++-- 11 files changed, 107 insertions(+), 59 deletions(-) create mode 100644 test/cli/fixtures/example-patient.csv diff --git a/src/extractors/CSVTreatmentPlanChangeExtractor.js b/src/extractors/CSVTreatmentPlanChangeExtractor.js index cc21eb7a..e7a291a5 100644 --- a/src/extractors/CSVTreatmentPlanChangeExtractor.js +++ b/src/extractors/CSVTreatmentPlanChangeExtractor.js @@ -3,6 +3,7 @@ const { BaseCSVExtractor } = require('./BaseCSVExtractor'); const { formatDate } = require('../helpers/dateUtils'); const { generateMcodeResources } = require('../templates'); const { getEmptyBundle } = require('../helpers/fhirUtils'); +const { getPatientFromContext } = require('../helpers/contextUtils'); const logger = require('../helpers/logger'); const { CSVTreatmentPlanChangeSchema } = require('../helpers/schemas/csv'); @@ -15,7 +16,7 @@ function formatData(tpcData, patientId) { return []; } - // Newly combined data has mrn and list of reviews to map to an extension + // Newly combined data has subjectId and list of reviews to map to an extension const combinedFormat = { subjectId: patientId, reviews: [] }; // If there are multiple entries, combine them into one object with multiple reviews @@ -75,15 +76,16 @@ class CSVTreatmentPlanChangeExtractor extends BaseCSVExtractor { return this.csvModule.get('mrn', mrn, fromDate, toDate); } - async get({ mrn, fromDate, toDate }) { + async get({ mrn, context, fromDate, toDate }) { const tpcData = await this.getTPCData(mrn, fromDate, toDate); if (tpcData.length === 0) { logger.warn('No treatment plan change data found for patient'); return getEmptyBundle(); } + const patientId = getPatientFromContext(context).id; // Reformat data - const formattedData = formatData(tpcData); + const formattedData = formatData(tpcData, patientId); // Fill templates return generateMcodeResources('CarePlanWithReview', formattedData); diff --git a/test/cli/fixtures/example-patient.csv b/test/cli/fixtures/example-patient.csv new file mode 100644 index 00000000..10822054 --- /dev/null +++ b/test/cli/fixtures/example-patient.csv @@ -0,0 +1,4 @@ +mrn,familyName,givenName,gender,birthsex,dateOfBirth,race,ethnicity,language,addressLine,city,state,zip +123,Doe,Jane,female,F,1980-01-01,2028-9,2186-5,en,2 West Side Rd,Malden,MA,02148 +456,Doe,John,male,M,1970-01-01,2106-3,2186-5,en-US,3 East Side Rd,Brooklyn,NY,11201 +789,Doe,Jimmy,other,UNK,1980-03-01,ASKU,2186-5,ar,,,,90001 diff --git a/test/cli/mcodeExtraction.test.js b/test/cli/mcodeExtraction.test.js index 4cc33215..42c453d7 100644 --- a/test/cli/mcodeExtraction.test.js +++ b/test/cli/mcodeExtraction.test.js @@ -46,6 +46,13 @@ describe('mcodeExtraction', () => { it('should succeed in extraction when CSV files do not have data for all patients', async () => { const testConfig = { extractors: [ + { + label: 'patients', + type: 'CSVPatientExtractor', + constructorArgs: { + filePath: path.join(__dirname, './fixtures/example-patient.csv'), + }, + }, { label: 'condition', type: 'CSVConditionExtractor', diff --git a/test/extractors/CSVStagingExtractor.test.js b/test/extractors/CSVStagingExtractor.test.js index e48c0c5b..c9eae1e4 100644 --- a/test/extractors/CSVStagingExtractor.test.js +++ b/test/extractors/CSVStagingExtractor.test.js @@ -4,9 +4,11 @@ const _ = require('lodash'); const { CSVStagingExtractor } = require('../../src/extractors'); const exampleStagingModuleResponse = require('./fixtures/csv-staging-module-response.json'); const exampleCSVStagingBundle = require('./fixtures/csv-staging-bundle.json'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); // Constants for tests -const MOCK_PATIENT_MRN = 'pat-mrn-1'; // linked to values in example-module-response above +const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response above const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); // need a valid path/csv here to avoid parse error // Instantiate module with parameters @@ -17,16 +19,17 @@ const { csvModule } = csvStagingExtractor; // Spy on csvModule const csvModuleSpy = jest.spyOn(csvModule, 'get'); - const formatTNMCategoryData = rewire('../../src/extractors/CSVStagingExtractor.js').__get__('formatTNMCategoryData'); describe('CSVStagingExtractor', () => { describe('formatTNMCategoryData', () => { test('should join data appropriately and throw errors when missing required properties', () => { - const expectedErrorString = 'Staging data is missing an expected property: mrn, conditionId, effectiveDate are required.'; + const expectedErrorString = 'Staging data is missing an expected property: conditionId, effectiveDate are required.'; const localData = _.cloneDeep(exampleStagingModuleResponse[0]); + const patientId = getPatientFromContext(MOCK_CONTEXT).id; + // Test that valid data works fine - expect(formatTNMCategoryData(localData)).toEqual(expect.anything()); + expect(formatTNMCategoryData(localData, patientId)).toEqual(expect.anything()); // Test all optional properties can be empty without issue localData.t = ''; @@ -41,7 +44,7 @@ describe('CSVStagingExtractor', () => { expect(formatTNMCategoryData(localData)).toEqual(expect.anything()); // Removing each required property should throw an error - const requiredKeys = ['mrn', 'conditionId', 'effectiveDate']; + const requiredKeys = ['conditionId', 'effectiveDate']; requiredKeys.forEach((key) => { const clonedData = _.cloneDeep(localData); clonedData[key] = ''; @@ -53,7 +56,7 @@ describe('CSVStagingExtractor', () => { describe('get', () => { test('should return bundle with Observation', async () => { csvModuleSpy.mockReturnValue(exampleStagingModuleResponse); - const data = await csvStagingExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvStagingExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); @@ -63,7 +66,7 @@ describe('CSVStagingExtractor', () => { test('should return empty bundle when no data available from module', async () => { csvModuleSpy.mockReturnValue([]); - const data = await csvStagingExtractor.get({ mrn: MOCK_PATIENT_MRN }); + const data = await csvStagingExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); expect(data.entry).toBeDefined(); diff --git a/test/extractors/CSVTreatmentPlanChangeExtractor.test.js b/test/extractors/CSVTreatmentPlanChangeExtractor.test.js index 6d920d9b..9849d208 100644 --- a/test/extractors/CSVTreatmentPlanChangeExtractor.test.js +++ b/test/extractors/CSVTreatmentPlanChangeExtractor.test.js @@ -4,18 +4,23 @@ const _ = require('lodash'); const { CSVTreatmentPlanChangeExtractor } = require('../../src/extractors'); const exampleCSVTPCModuleResponse = require('./fixtures/csv-treatment-plan-change-module-response.json'); const exampleCSVTPCBundle = require('./fixtures/csv-treatment-plan-change-bundle.json'); +const { getPatientFromContext } = require('../../src/helpers/contextUtils'); +const MOCK_CONTEXT = require('./fixtures/context-with-patient.json'); -const MOCK_MRN = 'mrn-1'; +// Constants for tests +const MOCK_PATIENT_MRN = 'mrn-1'; // linked to values in example-module-response above const MOCK_CSV_PATH = path.join(__dirname, 'fixtures/example.csv'); + +// Instantiate module with parameters const csvTPCExtractor = new CSVTreatmentPlanChangeExtractor({ filePath: MOCK_CSV_PATH, }); +// Destructure all modules const { csvModule } = csvTPCExtractor; // Spy on csvModule const csvModuleSpy = jest.spyOn(csvModule, 'get'); - const formatData = rewire('../../src/extractors/CSVTreatmentPlanChangeExtractor.js').__get__('formatData'); describe('CSVTreatmentPlanChangeExtractor', () => { @@ -33,19 +38,21 @@ describe('CSVTreatmentPlanChangeExtractor', () => { mrn: 'id', }, ]; + const patientId = getPatientFromContext(MOCK_CONTEXT).id; test('should join data appropriately and throw errors when missing required properties', () => { - const expectedErrorString = 'Treatment Plan Change Data missing an expected property: mrn, dateOfCarePlan, changed are required'; + const expectedErrorString = 'Treatment Plan Change Data missing an expected property: dateOfCarePlan, changed are required'; // formatData on example data should not throw error when changed is false - expect(() => formatData(exampleData)).not.toThrowError(); + expect(() => formatData(exampleData, patientId)).not.toThrowError(); // Test required properties throw error - Object.keys(exampleData[0]).forEach((key) => { + const requiredKeys = ['dateOfCarePlan', 'changed']; + requiredKeys.forEach((key) => { const clonedData = _.cloneDeep(exampleData); delete clonedData[0][key]; - expect(() => formatData(clonedData)).toThrow(new Error(expectedErrorString)); + expect(() => formatData(clonedData, patientId)).toThrow(new Error(expectedErrorString)); }); }); @@ -54,17 +61,17 @@ describe('CSVTreatmentPlanChangeExtractor', () => { // error should get throw when changed flag is true and there is no reasonCode provided exampleData[0].changed = 'true'; - expect(() => formatData(exampleData)).toThrow(new Error(expectedErrorString)); + expect(() => formatData(exampleData, patientId)).toThrow(new Error(expectedErrorString)); // No error should be throw when reasonCode is provided exampleData[0].reasonCode = 'example code'; - expect(() => formatData(exampleData)).not.toThrowError(); + expect(() => formatData(exampleData, patientId)).not.toThrowError(); }); test('should join multiple entries into one', () => { const expectedFormattedData = [ { - mrn: 'mrn-1', + subjectId: 'mrn-1', reviews: [ { effectiveDate: '2020-04-15', @@ -81,14 +88,14 @@ describe('CSVTreatmentPlanChangeExtractor', () => { }, ]; - expect(formatData(exampleCSVTPCModuleResponse)).toEqual(expectedFormattedData); + expect(formatData(exampleCSVTPCModuleResponse, patientId)).toEqual(expectedFormattedData); }); }); describe('get', () => { test('should return bundle with CarePlan', async () => { csvModuleSpy.mockReturnValue(exampleCSVTPCModuleResponse); - const data = await csvTPCExtractor.get({ mrn: MOCK_MRN }); + const data = await csvTPCExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); @@ -98,7 +105,7 @@ describe('CSVTreatmentPlanChangeExtractor', () => { test('should return empty bundle with no data available from module', async () => { csvModuleSpy.mockReturnValue([]); - const data = await csvTPCExtractor.get({ mrn: MOCK_MRN }); + const data = await csvTPCExtractor.get({ mrn: MOCK_PATIENT_MRN, context: MOCK_CONTEXT }); expect(data.resourceType).toEqual('Bundle'); expect(data.type).toEqual('collection'); diff --git a/test/extractors/fixtures/csv-staging-bundle.json b/test/extractors/fixtures/csv-staging-bundle.json index 2dc218b9..26328b3b 100644 --- a/test/extractors/fixtures/csv-staging-bundle.json +++ b/test/extractors/fixtures/csv-staging-bundle.json @@ -3,10 +3,10 @@ "type": "collection", "entry": [ { - "fullUrl": "urn:uuid:ed5faf7f2a1cef061d7584afd463e60eb7202104700647e92fbb7c9419eb7897", + "fullUrl": "urn:uuid:626b61fd700d8ddc5a6a576a2e47944191b5f608e03083d00c49614f0158bb65", "resource": { "resourceType": "Observation", - "id": "ed5faf7f2a1cef061d7584afd463e60eb7202104700647e92fbb7c9419eb7897", + "id": "626b61fd700d8ddc5a6a576a2e47944191b5f608e03083d00c49614f0158bb65", "meta": { "profile": [ "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-tnm-clinical-primary-tumor-category" @@ -32,7 +32,7 @@ ] }, "subject": { - "reference": "urn:uuid:pat-mrn-1", + "reference": "urn:uuid:mrn-1", "type": "Patient" }, "method": { @@ -61,10 +61,10 @@ } }, { - "fullUrl": "urn:uuid:88992af10e3a822f2a946628de91012d5afae49a25d1641db09e9d6ed4ca24c3", + "fullUrl": "urn:uuid:66e0e5f216bebeba287c3a3b0e220170bab88da95030e0c4bdba32f66628d00c", "resource": { "resourceType": "Observation", - "id": "88992af10e3a822f2a946628de91012d5afae49a25d1641db09e9d6ed4ca24c3", + "id": "66e0e5f216bebeba287c3a3b0e220170bab88da95030e0c4bdba32f66628d00c", "meta": { "profile": [ "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-tnm-clinical-distant-metastases-category" @@ -90,7 +90,7 @@ ] }, "subject": { - "reference": "urn:uuid:pat-mrn-1", + "reference": "urn:uuid:mrn-1", "type": "Patient" }, "method": { @@ -119,10 +119,10 @@ } }, { - "fullUrl": "urn:uuid:1373d3fb234cad0b03912cfbba16bdc4eabbd6217d83b838f6d9c1343bc9b394", + "fullUrl": "urn:uuid:4469acab85a889d722f5b8b1cb786df8adbfc5f0b50ac23393ec42b8ecfd49e9", "resource": { "resourceType": "Observation", - "id": "1373d3fb234cad0b03912cfbba16bdc4eabbd6217d83b838f6d9c1343bc9b394", + "id": "4469acab85a889d722f5b8b1cb786df8adbfc5f0b50ac23393ec42b8ecfd49e9", "meta": { "profile": [ "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-tnm-clinical-regional-nodes-category" @@ -148,7 +148,7 @@ ] }, "subject": { - "reference": "urn:uuid:pat-mrn-1", + "reference": "urn:uuid:mrn-1", "type": "Patient" }, "method": { @@ -177,10 +177,10 @@ } }, { - "fullUrl": "urn:uuid:23564150098c6afeb2abc712086a21ead2e9bd308929f9d290988c6921ac841b", + "fullUrl": "urn:uuid:8fca0c2922335ea3c92022b4b2d4de17ba0645ce72d884b1e1c71a757c300a10", "resource": { "resourceType": "Observation", - "id": "23564150098c6afeb2abc712086a21ead2e9bd308929f9d290988c6921ac841b", + "id": "8fca0c2922335ea3c92022b4b2d4de17ba0645ce72d884b1e1c71a757c300a10", "meta": { "profile": [ "http://hl7.org/fhir/us/mcode/StructureDefinition/mcode-tnm-clinical-stage-group" @@ -214,7 +214,7 @@ ] }, "subject": { - "reference": "urn:uuid:pat-mrn-1", + "reference": "urn:uuid:mrn-1", "type": "Patient" }, "effectiveDateTime": "2020-01-01", @@ -234,15 +234,15 @@ ], "hasMember": [ { - "reference": "urn:uuid:ed5faf7f2a1cef061d7584afd463e60eb7202104700647e92fbb7c9419eb7897", + "reference": "urn:uuid:626b61fd700d8ddc5a6a576a2e47944191b5f608e03083d00c49614f0158bb65", "type": "Observation" }, { - "reference": "urn:uuid:88992af10e3a822f2a946628de91012d5afae49a25d1641db09e9d6ed4ca24c3", + "reference": "urn:uuid:66e0e5f216bebeba287c3a3b0e220170bab88da95030e0c4bdba32f66628d00c", "type": "Observation" }, { - "reference": "urn:uuid:1373d3fb234cad0b03912cfbba16bdc4eabbd6217d83b838f6d9c1343bc9b394", + "reference": "urn:uuid:4469acab85a889d722f5b8b1cb786df8adbfc5f0b50ac23393ec42b8ecfd49e9", "type": "Observation" } ] diff --git a/test/extractors/fixtures/csv-staging-module-response.json b/test/extractors/fixtures/csv-staging-module-response.json index bfa4247a..e84c26a5 100644 --- a/test/extractors/fixtures/csv-staging-module-response.json +++ b/test/extractors/fixtures/csv-staging-module-response.json @@ -1,6 +1,6 @@ [ { - "mrn": "pat-mrn-1", + "mrn": "mrn-1", "conditionId": "cond-1", "stageGroup": "3C", "t": "cT3", diff --git a/test/extractors/fixtures/csv-treatment-plan-change-bundle.json b/test/extractors/fixtures/csv-treatment-plan-change-bundle.json index 51cec659..73974a89 100644 --- a/test/extractors/fixtures/csv-treatment-plan-change-bundle.json +++ b/test/extractors/fixtures/csv-treatment-plan-change-bundle.json @@ -3,10 +3,10 @@ "type": "collection", "entry": [ { - "fullUrl": "urn:uuid:3005b559bb913fdfb23756a442645c6c37bbdafa42ad4be0bd1a6b698274f466", + "fullUrl": "urn:uuid:86e98914af1979c1b3dd780ebda05a677fc05939068f587949f886f1f3de22ed", "resource": { "resourceType": "CarePlan", - "id": "3005b559bb913fdfb23756a442645c6c37bbdafa42ad4be0bd1a6b698274f466", + "id": "86e98914af1979c1b3dd780ebda05a677fc05939068f587949f886f1f3de22ed", "meta": { "profile": [ "http://mcodeinitiative.org/codex/us/icare/StructureDefinition/icare-care-plan-with-review" @@ -24,13 +24,23 @@ "url": "CarePlanChangeReason", "valueCodeableConcept": { "coding": [ - { "system": "http://snomed.info/sct", "code": "281647001", "display": "Adverse reaction (disorder)" } + { + "system": "http://snomed.info/sct", + "code": "281647001", + "display": "Adverse reaction (disorder)" + } ], "text": "Adverse reaction (disorder)" } }, - { "url": "ReviewDate", "valueDate": "2020-04-15" }, - { "url": "ChangedFlag", "valueBoolean": true } + { + "url": "ReviewDate", + "valueDate": "2020-04-15" + }, + { + "url": "ChangedFlag", + "valueBoolean": true + } ] }, { @@ -40,22 +50,37 @@ "url": "CarePlanChangeReason", "valueCodeableConcept": { "coding": [ - { "system": "http://snomed.info/sct", "code": "405613005" } + { + "system": "http://snomed.info/sct", + "code": "405613005" + } ] } }, - { "url": "ReviewDate", "valueDate": "2020-04-30" }, - { "url": "ChangedFlag", "valueBoolean": true } + { + "url": "ReviewDate", + "valueDate": "2020-04-30" + }, + { + "url": "ChangedFlag", + "valueBoolean": true + } ] } ], - "subject": { "reference": "urn:uuid:mrn-1", "type": "Patient" }, + "subject": { + "reference": "urn:uuid:mrn-1", + "type": "Patient" + }, "status": "draft", "intent": "proposal", "category": [ { "coding": [ - { "code": "assess-plan", "system": "http://argonaut.hl7.org" } + { + "code": "assess-plan", + "system": "http://argonaut.hl7.org" + } ] } ] diff --git a/test/extractors/fixtures/csv-treatment-plan-change-module-response.json b/test/extractors/fixtures/csv-treatment-plan-change-module-response.json index 51baf2e4..c049a026 100644 --- a/test/extractors/fixtures/csv-treatment-plan-change-module-response.json +++ b/test/extractors/fixtures/csv-treatment-plan-change-module-response.json @@ -1,13 +1,13 @@ [ { - "mrn": "mrn-1", + "subjectId": "mrn-1", "dateOfCarePlan": "2020-04-15", "reasonCode": "281647001", "reasonDisplayText": "Adverse reaction (disorder)", "changed": "true" }, { - "mrn": "mrn-1", + "subjectId": "mrn-1", "dateOfCarePlan": "2020-04-30", "reasonCode": "405613005", "changed": "true" diff --git a/test/templates/carePlanWithReview.test.js b/test/templates/carePlanWithReview.test.js index 13dba799..ab2b9281 100644 --- a/test/templates/carePlanWithReview.test.js +++ b/test/templates/carePlanWithReview.test.js @@ -8,7 +8,7 @@ describe('JavaScript render CarePlan template', () => { test('minimal required data passed into template should generate FHIR resource', () => { const CARE_PLAN_VALID_DATA = { id: 'test-id', - mrn: 'abc-def', + subjectId: 'abc-def', name: null, reviews: [ { @@ -28,7 +28,7 @@ describe('JavaScript render CarePlan template', () => { test('maximal data passed into template should generate FHIR resource', () => { const MAX_CARE_PLAN_DATA = { id: 'test-id', - mrn: 'abc-def', + subjectId: 'abc-def', name: 'Sample Text', reviews: [ { @@ -54,7 +54,7 @@ describe('JavaScript render CarePlan template', () => { test('missing non-required data at care plan object level should not throw an error', () => { const NECESSARY_DATA = { id: 'test-id', - mrn: 'abc-def', + subjectId: 'abc-def', reviews: [], }; @@ -68,7 +68,7 @@ describe('JavaScript render CarePlan template', () => { test('missing non-required data at review object level should not throw an error', () => { const NECESSARY_DATA = { id: 'test-id', - mrn: 'abc-def', + subjectId: 'abc-def', reviews: [ { effectiveDate: '2020-01-23', @@ -100,7 +100,7 @@ describe('JavaScript render CarePlan template', () => { const INVALID_REVIEW_DATA = { // Omitting 'hasChanged' field on the review which is a required property id: 'test-id', - mrn: 'abc-def', + subjectId: 'abc-def', reviews: [{ effectiveDate: '2020-01-23', hasChanged: null, diff --git a/test/templates/medication.test.js b/test/templates/medication.test.js index ca37eeb2..fad7ac7a 100644 --- a/test/templates/medication.test.js +++ b/test/templates/medication.test.js @@ -5,7 +5,7 @@ const { cancerRelatedMedicationTemplate } = require('../../src/templates/CancerR const { allOptionalKeyCombinationsNotThrow } = require('../utils'); const MEDICATION_VALID_DATA = { - mrn: 'mrn-1', + subjectId: 'mrn-1', id: 'medicationId-1', code: 'example-code', codeSystem: 'example-code-system', @@ -21,7 +21,7 @@ const MEDICATION_VALID_DATA = { const MEDICATION_MINIMAL_DATA = { // Only include 'mrn', 'code', 'codesystem', and 'status' fields which are required - mrn: 'mrn-1', + subjectId: 'mrn-1', code: 'example-code', codeSystem: 'example-code-system', status: 'example-status', @@ -37,7 +37,7 @@ const MEDICATION_MINIMAL_DATA = { const MEDICATION_INVALID_DATA = { // Omitting 'mrn', 'code', 'codesystem', and 'status' fields which are required - mrn: null, + subjectId: null, code: null, codeSystem: null, status: null, @@ -90,7 +90,7 @@ describe('test Medication template', () => { }; const NECESSARY_DATA = { - mrn: 'mrn-1', + subjectId: 'mrn-1', code: 'example-code', codeSystem: 'example-code-system', status: 'example-status', From e3b68e6a090294322971057c0bf5df5a15c590b8 Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Fri, 16 Apr 2021 11:06:03 -0400 Subject: [PATCH 07/10] staging template uses patientId instead of MRN --- src/extractors/CSVStagingExtractor.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/extractors/CSVStagingExtractor.js b/src/extractors/CSVStagingExtractor.js index ba3cc271..f10cffe7 100644 --- a/src/extractors/CSVStagingExtractor.js +++ b/src/extractors/CSVStagingExtractor.js @@ -33,13 +33,13 @@ function formatTNMCategoryData(stagingData, patientId) { return formattedData; } -function formatStagingData(stagingData, categoryIds) { +function formatStagingData(stagingData, categoryIds, patientId) { const { - mrn, conditionId, type, stageGroup, stagingSystem, stagingCodeSystem, effectiveDate, + conditionId, type, stageGroup, stagingSystem, stagingCodeSystem, effectiveDate, } = stagingData; return { - subjectId: mrn, + subjectId: patientId, conditionId, type, stageGroup, @@ -77,7 +77,7 @@ class CSVStagingExtractor extends BaseCSVExtractor { const mcodeCategoryResources = formattedCategoryData.map((d) => firstEntryInBundle(generateMcodeResources('TNMCategory', d))); // Pass category resource ids to formatStagingData - const formattedStagingData = formatStagingData(data, mcodeCategoryResources.map((r) => r.resource.id)); + const formattedStagingData = formatStagingData(data, mcodeCategoryResources.map((r) => r.resource.id), patientId); const stagingResource = firstEntryInBundle(generateMcodeResources('Staging', formattedStagingData)); // Push all resources into entryResources From 65c6ff71b2c69d9329d31773031f054784afee78 Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Tue, 20 Apr 2021 09:33:12 -0400 Subject: [PATCH 08/10] Update src/extractors/CSVCancerRelatedMedicationExtractor.js --- src/extractors/CSVCancerRelatedMedicationExtractor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extractors/CSVCancerRelatedMedicationExtractor.js b/src/extractors/CSVCancerRelatedMedicationExtractor.js index 5d9e1123..336d9a24 100644 --- a/src/extractors/CSVCancerRelatedMedicationExtractor.js +++ b/src/extractors/CSVCancerRelatedMedicationExtractor.js @@ -15,7 +15,7 @@ function formatData(medicationData, patientId) { } = medication; if (!(code && codeSystem && status)) { - throw new Error('The cancer-related medication is missing an expected element; mrn, code, code system, and status are all required values.'); + throw new Error('The cancer-related medication is missing an expected element; code, code system, and status are all required values.'); } return { From 39ac9e27bf4c1cd77207057e1d9cc575328a2b11 Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Tue, 20 Apr 2021 09:45:39 -0400 Subject: [PATCH 09/10] update error message expectations --- test/extractors/CSVCancerRelatedMedicationExtractor.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extractors/CSVCancerRelatedMedicationExtractor.test.js b/test/extractors/CSVCancerRelatedMedicationExtractor.test.js index 2473ac67..0d65bb14 100644 --- a/test/extractors/CSVCancerRelatedMedicationExtractor.test.js +++ b/test/extractors/CSVCancerRelatedMedicationExtractor.test.js @@ -35,7 +35,7 @@ expandedExampleBundle.entry.push(exampleCSVMedicationBundle.entry[0]); describe('CSVCancerRelatedMedicationExtractor', () => { describe('formatData', () => { test('should join data appropriately and throw errors when missing required properties', () => { - const expectedErrorString = 'The cancer-related medication is missing an expected element; mrn, code, code system, and status are all required values.'; + const expectedErrorString = 'The cancer-related medication is missing an expected element; code, code system, and status are all required values.'; const localData = _.cloneDeep(exampleCSVMedicationModuleResponse); const patientId = getPatientFromContext(MOCK_CONTEXT).id; From 98c5b37717dfdf278bb74d0da6ee97839e2b8546 Mon Sep 17 00:00:00 2001 From: Dylan Phelan Date: Tue, 20 Apr 2021 13:41:39 -0400 Subject: [PATCH 10/10] udpating order of extractors --- config/csv.config.example.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/csv.config.example.json b/config/csv.config.example.json index 9c936e9f..66862dda 100644 --- a/config/csv.config.example.json +++ b/config/csv.config.example.json @@ -13,17 +13,17 @@ }, "extractors": [ { - "label": "condition", - "type": "CSVConditionExtractor", + "label": "patient", + "type": "CSVPatientExtractor", "constructorArgs": { - "filePath": "./data/condition-information.csv" + "filePath": "./data/patient-information.csv" } }, { - "label": "patient", - "type": "CSVPatientExtractor", + "label": "condition", + "type": "CSVConditionExtractor", "constructorArgs": { - "filePath": "./data/patient-information.csv" + "filePath": "./data/condition-information.csv" } }, {