Skip to content

Commit 2661ee7

Browse files
committed
Moving MRN masking to maskPatientData
1 parent d5e3013 commit 2661ee7

File tree

5 files changed

+11
-90
lines changed

5 files changed

+11
-90
lines changed

src/application/app.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const logger = require('../helpers/logger');
55
const { RunInstanceLogger } = require('./tools/RunInstanceLogger');
66
const { sendEmailNotification, zipErrors } = require('./tools/emailNotifications');
77
const { extractDataForPatients } = require('./tools/mcodeExtraction');
8-
const { maskMRN } = require('../helpers/patientUtils');
98
const { parsePatientIds } = require('../helpers/appUtils');
109

1110
function getConfig(pathToConfig) {
@@ -78,23 +77,6 @@ async function mcodeApp(Client, fromDate, toDate, pathToConfig, pathToRunLogs, d
7877
}
7978
}
8079

81-
// check if config specifies that MRN needs to be masked
82-
// if it does need to be masked, mask all references to MRN outside of the patient resource
83-
const patientConfig = config.extractors.find((e) => e.type === 'CSVPatientExtractor');
84-
if (patientConfig && ('constructorArgs' in patientConfig && 'mask' in patientConfig.constructorArgs)) {
85-
if (patientConfig.constructorArgs.mask.includes('mrn')) {
86-
extractedData.forEach((bundle, i) => {
87-
// NOTE: This may fail to mask MRN-related properties on non-patient resources
88-
// Need to investigate further.
89-
try {
90-
maskMRN(bundle);
91-
} catch (e) {
92-
logger.error(`Bundle ${i + 1}: ${e.message}`);
93-
}
94-
});
95-
}
96-
}
97-
9880
return extractedData;
9981
}
10082

src/helpers/patientUtils.js

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ function maskPatientData(bundle, mask) {
103103
delete patient._gender; // gender may have a dataAbsentReason on it for 'unknown' data, but we'll still want to mask it
104104
patient._gender = masked;
105105
} else if (field === 'mrn' && 'identifier' in patient) {
106+
// id and fullURL still need valid values, so we use a hashed version of MRN instead of dataAbsentReason
107+
const maskedMRN = shajs('sha256').update(patient.id).digest('hex');
108+
patient.id = maskedMRN;
109+
const patientEntry = fhirpath.evaluate(
110+
bundle,
111+
'Bundle.entry.where(resource.resourceType=\'Patient\')',
112+
)[0];
113+
patientEntry.fullUrl = `urn:uuid:${maskedMRN}`;
106114
patient.identifier = [masked];
107115
} else if (field === 'name' && 'name' in patient) {
108116
patient.name = [masked];
@@ -151,33 +159,10 @@ function maskPatientData(bundle, mask) {
151159
});
152160
}
153161

154-
/**
155-
* Mask all references to the MRN used as an id
156-
* Currently, the MRN appears as an id in 'subject' and 'individual' objects in other resources
157-
* and in the 'id' and 'fullUrl' fields of the Patient resource.
158-
* Replaces the MRN with a hash of the MRN
159-
* @param {Object} bundle a FHIR bundle with a Patient resource and other resources
160-
*/
161-
function maskMRN(bundle) {
162-
const patient = fhirpath.evaluate(bundle, 'Bundle.entry.where(resource.resourceType=\'Patient\')')[0];
163-
if (patient === undefined) throw Error('No Patient resource in bundle. Could not mask MRN.');
164-
const mrn = patient.resource.id;
165-
const masked = shajs('sha256').update(mrn).digest('hex');
166-
patient.fullUrl = `urn:uuid:${masked}`;
167-
patient.resource.id = masked;
168-
const subjects = fhirpath.evaluate(bundle, `Bundle.entry.resource.subject.where(reference='urn:uuid:${mrn}')`);
169-
const individuals = fhirpath.evaluate(bundle, `Bundle.entry.resource.individual.where(reference='urn:uuid:${mrn}')`);
170-
const mrnOccurrences = subjects.concat(individuals);
171-
for (let i = 0; i < mrnOccurrences.length; i += 1) {
172-
mrnOccurrences[i].reference = `urn:uuid:${masked}`;
173-
}
174-
}
175-
176162
module.exports = {
177163
getEthnicityDisplay,
178164
getRaceCodesystem,
179165
getRaceDisplay,
180166
getPatientName,
181167
maskPatientData,
182-
maskMRN,
183168
};

test/helpers/fixtures/bundle-with-mrn-id.json

Lines changed: 0 additions & 30 deletions
This file was deleted.

test/helpers/fixtures/masked-patient-bundle.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
"type": "collection",
44
"entry": [
55
{
6-
"fullUrl": "urn:uuid:119147111821125",
6+
"fullUrl": "urn:uuid:be7e757c18743bf94cee7764b960eb69867b430232f759e706d080604fe416ad",
77
"resource": {
88
"resourceType": "Patient",
9-
"id": "119147111821125",
9+
"id": "be7e757c18743bf94cee7764b960eb69867b430232f759e706d080604fe416ad",
1010
"identifier": [
1111
{
1212
"extension": [

test/helpers/patientUtils.test.js

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
const _ = require('lodash');
2-
const shajs = require('sha.js');
32
const {
4-
getEthnicityDisplay, getRaceCodesystem, getRaceDisplay, getPatientName, maskPatientData, maskMRN,
3+
getEthnicityDisplay, getRaceCodesystem, getRaceDisplay, getPatientName, maskPatientData,
54
} = require('../../src/helpers/patientUtils');
65
const examplePatient = require('../extractors/fixtures/csv-patient-bundle.json');
76
const exampleMaskedPatient = require('./fixtures/masked-patient-bundle.json');
8-
const exampleBundleWithMRN = require('./fixtures/bundle-with-mrn-id.json');
97

108
describe('PatientUtils', () => {
119
describe('getEthnicityDisplay', () => {
@@ -119,18 +117,4 @@ describe('PatientUtils', () => {
119117
expect(() => maskPatientData(bundle, ['this is an invalid field', 'mrn'])).toThrowError();
120118
});
121119
});
122-
describe('maskMRN', () => {
123-
test('all occurances of the MRN as an id should be masked by a hashed version', () => {
124-
const bundle = _.cloneDeep(exampleBundleWithMRN);
125-
const hashedMRN = shajs('sha256').update(bundle.entry[0].resource.id).digest('hex');
126-
maskMRN(bundle);
127-
expect(bundle.entry[0].resource.id).toEqual(hashedMRN);
128-
expect(bundle.entry[0].fullUrl).toEqual(`urn:uuid:${hashedMRN}`);
129-
expect(bundle.entry[1].resource.subject.reference).toEqual(`urn:uuid:${hashedMRN}`);
130-
expect(bundle.entry[2].resource.individual.reference).toEqual(`urn:uuid:${hashedMRN}`);
131-
});
132-
test('should throw error when there is no Patient resource in bundle', () => {
133-
expect(() => maskMRN({})).toThrowError();
134-
});
135-
});
136120
});

0 commit comments

Comments
 (0)