From 2661ee7208e4f169a49aba89f2dec653bd1b6b80 Mon Sep 17 00:00:00 2001 From: Dylan Mendelowitz Date: Tue, 29 Jun 2021 13:35:41 -0400 Subject: [PATCH] Moving MRN masking to maskPatientData --- src/application/app.js | 18 ----------- src/helpers/patientUtils.js | 31 +++++-------------- test/helpers/fixtures/bundle-with-mrn-id.json | 30 ------------------ .../fixtures/masked-patient-bundle.json | 4 +-- test/helpers/patientUtils.test.js | 18 +---------- 5 files changed, 11 insertions(+), 90 deletions(-) delete mode 100644 test/helpers/fixtures/bundle-with-mrn-id.json diff --git a/src/application/app.js b/src/application/app.js index d61761c4..0354d7e9 100644 --- a/src/application/app.js +++ b/src/application/app.js @@ -5,7 +5,6 @@ const logger = require('../helpers/logger'); const { RunInstanceLogger } = require('./tools/RunInstanceLogger'); const { sendEmailNotification, zipErrors } = require('./tools/emailNotifications'); const { extractDataForPatients } = require('./tools/mcodeExtraction'); -const { maskMRN } = require('../helpers/patientUtils'); const { parsePatientIds } = require('../helpers/appUtils'); function getConfig(pathToConfig) { @@ -78,23 +77,6 @@ async function mcodeApp(Client, fromDate, toDate, pathToConfig, pathToRunLogs, d } } - // check if config specifies that MRN needs to be masked - // if it does need to be masked, mask all references to MRN outside of the patient resource - const patientConfig = config.extractors.find((e) => e.type === 'CSVPatientExtractor'); - if (patientConfig && ('constructorArgs' in patientConfig && 'mask' in patientConfig.constructorArgs)) { - if (patientConfig.constructorArgs.mask.includes('mrn')) { - extractedData.forEach((bundle, i) => { - // NOTE: This may fail to mask MRN-related properties on non-patient resources - // Need to investigate further. - try { - maskMRN(bundle); - } catch (e) { - logger.error(`Bundle ${i + 1}: ${e.message}`); - } - }); - } - } - return extractedData; } diff --git a/src/helpers/patientUtils.js b/src/helpers/patientUtils.js index babd76d6..7dbf734a 100644 --- a/src/helpers/patientUtils.js +++ b/src/helpers/patientUtils.js @@ -103,6 +103,14 @@ function maskPatientData(bundle, mask) { delete patient._gender; // gender may have a dataAbsentReason on it for 'unknown' data, but we'll still want to mask it patient._gender = masked; } else if (field === 'mrn' && 'identifier' in patient) { + // id and fullURL still need valid values, so we use a hashed version of MRN instead of dataAbsentReason + const maskedMRN = shajs('sha256').update(patient.id).digest('hex'); + patient.id = maskedMRN; + const patientEntry = fhirpath.evaluate( + bundle, + 'Bundle.entry.where(resource.resourceType=\'Patient\')', + )[0]; + patientEntry.fullUrl = `urn:uuid:${maskedMRN}`; patient.identifier = [masked]; } else if (field === 'name' && 'name' in patient) { patient.name = [masked]; @@ -151,33 +159,10 @@ function maskPatientData(bundle, mask) { }); } -/** - * Mask all references to the MRN used as an id - * Currently, the MRN appears as an id in 'subject' and 'individual' objects in other resources - * and in the 'id' and 'fullUrl' fields of the Patient resource. - * Replaces the MRN with a hash of the MRN - * @param {Object} bundle a FHIR bundle with a Patient resource and other resources - */ -function maskMRN(bundle) { - const patient = fhirpath.evaluate(bundle, 'Bundle.entry.where(resource.resourceType=\'Patient\')')[0]; - if (patient === undefined) throw Error('No Patient resource in bundle. Could not mask MRN.'); - const mrn = patient.resource.id; - const masked = shajs('sha256').update(mrn).digest('hex'); - patient.fullUrl = `urn:uuid:${masked}`; - patient.resource.id = masked; - const subjects = fhirpath.evaluate(bundle, `Bundle.entry.resource.subject.where(reference='urn:uuid:${mrn}')`); - const individuals = fhirpath.evaluate(bundle, `Bundle.entry.resource.individual.where(reference='urn:uuid:${mrn}')`); - const mrnOccurrences = subjects.concat(individuals); - for (let i = 0; i < mrnOccurrences.length; i += 1) { - mrnOccurrences[i].reference = `urn:uuid:${masked}`; - } -} - module.exports = { getEthnicityDisplay, getRaceCodesystem, getRaceDisplay, getPatientName, maskPatientData, - maskMRN, }; diff --git a/test/helpers/fixtures/bundle-with-mrn-id.json b/test/helpers/fixtures/bundle-with-mrn-id.json deleted file mode 100644 index e4622a62..00000000 --- a/test/helpers/fixtures/bundle-with-mrn-id.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "resourceType": "Bundle", - "type": "collection", - "entry": [ - { - "fullUrl": "urn:uuid:123", - "resource": { - "resourceType": "Patient", - "id": "123" - - } - }, - { - "resource": { - "subject": { - "reference": "urn:uuid:123", - "type": "Patient" - } - } - }, - { - "resource": { - "individual": { - "reference": "urn:uuid:123", - "type": "Patient" - } - } - } - ] -} \ No newline at end of file diff --git a/test/helpers/fixtures/masked-patient-bundle.json b/test/helpers/fixtures/masked-patient-bundle.json index 3b7154ce..6e9bb78b 100644 --- a/test/helpers/fixtures/masked-patient-bundle.json +++ b/test/helpers/fixtures/masked-patient-bundle.json @@ -3,10 +3,10 @@ "type": "collection", "entry": [ { - "fullUrl": "urn:uuid:119147111821125", + "fullUrl": "urn:uuid:be7e757c18743bf94cee7764b960eb69867b430232f759e706d080604fe416ad", "resource": { "resourceType": "Patient", - "id": "119147111821125", + "id": "be7e757c18743bf94cee7764b960eb69867b430232f759e706d080604fe416ad", "identifier": [ { "extension": [ diff --git a/test/helpers/patientUtils.test.js b/test/helpers/patientUtils.test.js index 6cb33c29..288c5a37 100644 --- a/test/helpers/patientUtils.test.js +++ b/test/helpers/patientUtils.test.js @@ -1,11 +1,9 @@ const _ = require('lodash'); -const shajs = require('sha.js'); const { - getEthnicityDisplay, getRaceCodesystem, getRaceDisplay, getPatientName, maskPatientData, maskMRN, + getEthnicityDisplay, getRaceCodesystem, getRaceDisplay, getPatientName, maskPatientData, } = require('../../src/helpers/patientUtils'); const examplePatient = require('../extractors/fixtures/csv-patient-bundle.json'); const exampleMaskedPatient = require('./fixtures/masked-patient-bundle.json'); -const exampleBundleWithMRN = require('./fixtures/bundle-with-mrn-id.json'); describe('PatientUtils', () => { describe('getEthnicityDisplay', () => { @@ -119,18 +117,4 @@ describe('PatientUtils', () => { expect(() => maskPatientData(bundle, ['this is an invalid field', 'mrn'])).toThrowError(); }); }); - describe('maskMRN', () => { - test('all occurances of the MRN as an id should be masked by a hashed version', () => { - const bundle = _.cloneDeep(exampleBundleWithMRN); - const hashedMRN = shajs('sha256').update(bundle.entry[0].resource.id).digest('hex'); - maskMRN(bundle); - expect(bundle.entry[0].resource.id).toEqual(hashedMRN); - expect(bundle.entry[0].fullUrl).toEqual(`urn:uuid:${hashedMRN}`); - expect(bundle.entry[1].resource.subject.reference).toEqual(`urn:uuid:${hashedMRN}`); - expect(bundle.entry[2].resource.individual.reference).toEqual(`urn:uuid:${hashedMRN}`); - }); - test('should throw error when there is no Patient resource in bundle', () => { - expect(() => maskMRN({})).toThrowError(); - }); - }); });