diff --git a/README.md b/README.md index d37b0063..a8abbb63 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ npm start -- --entries-filter --from-date YYYY-MM-DD --to-date YYY-MM-DD --run-l ### Masking Patient Data Patient data can be masked within the extracted `Patient` resource. When masked, the value of the field will be replaced with a [Data Absent Reason extension](https://www.hl7.org/fhir/extension-data-absent-reason.html) with the code `masked`. -Patient properties that can be masked are: `gender`, `mrn`, `name`, `address`, `birthDate`, `language`, `ethnicity`, `birthsex`, and `race`. +Patient properties that can be masked are: `gender`, `mrn`, `name`, `address`, `birthDate`, `language`, `ethnicity`, `birthsex`, `race`, `telecom`, `multipleBirth`, `photo`, `contact`, `generalPractitioner`, `managingOrganization`, and `link`. To mask a property, provide an array of the properties to mask in the `constructorArgs` of the Patient extractor. For example, the following configuration can be used to mask `address` and `birthDate`: ```bash diff --git a/src/helpers/patientUtils.js b/src/helpers/patientUtils.js index 3c652ef9..34a18226 100644 --- a/src/helpers/patientUtils.js +++ b/src/helpers/patientUtils.js @@ -78,7 +78,9 @@ function getPatientName(name) { * dataAbsentReason extension with value 'masked' * @param {Object} bundle a FHIR bundle with a Patient resource * @param {Array} mask an array of fields to mask. Values can be: - * ['gender','mrn','name','address','birthDate','language','ethnicity','birthsex','race'] + * 'gender','mrn','name','address','birthDate','language','ethnicity','birthsex', + * 'race', 'telecom', 'multipleBirth', 'photo', 'contact', 'generalPractitioner', + * 'managingOrganization', and 'link' */ function maskPatientData(bundle, mask) { // get Patient resource from bundle @@ -87,7 +89,24 @@ function maskPatientData(bundle, mask) { 'Bundle.entry.where(resource.resourceType=\'Patient\').resource,first()', )[0]; - const validFields = ['gender', 'mrn', 'name', 'address', 'birthDate', 'language', 'ethnicity', 'birthsex', 'race']; + const validFields = [ + 'gender', + 'mrn', + 'name', + 'address', + 'birthDate', + 'language', + 'ethnicity', + 'birthsex', + 'race', + 'telecom', + 'multipleBirth', + 'photo', + 'contact', + 'generalPractitioner', + 'managingOrganization', + 'link', + ]; const masked = extensionArr(dataAbsentReasonExtension('masked')); mask.forEach((field) => { @@ -157,6 +176,32 @@ function maskPatientData(bundle, mask) { delete ethnicity[0].extension[1].valueString; ethnicity[0].extension[1]._valueString = masked; } + } else if (field === 'telecom' && 'telecom' in patient) { + delete patient.telecom; + patient.telecom = [masked]; + } else if (field === 'multipleBirth') { + if ('multipleBirthBoolean' in patient) { + delete patient.multipleBirthBoolean; + patient._multipleBirthBoolean = masked; + } else if ('multipleBirthInteger' in patient) { + delete patient.multipleBirthInteger; + patient._multipleBirthInteger = masked; + } + } else if (field === 'photo' && 'photo' in patient) { + delete patient.photo; + patient.photo = [masked]; + } else if (field === 'contact' && 'contact' in patient) { + delete patient.contact; + patient.contact = [masked]; + } else if (field === 'generalPractitioner' && 'generalPractitioner' in patient) { + delete patient.generalPractitioner; + patient.generalPractitioner = [masked]; + } else if (field === 'managingOrganization' && 'managingOrganization' in patient) { + delete patient.managingOrganization; + patient.managingOrganization = masked; + } else if (field === 'link' && 'link' in patient) { + delete patient.link; + patient.link = [masked]; } }); } diff --git a/test/extractors/fixtures/extended-patient-bundle.json b/test/extractors/fixtures/extended-patient-bundle.json new file mode 100644 index 00000000..872ee155 --- /dev/null +++ b/test/extractors/fixtures/extended-patient-bundle.json @@ -0,0 +1,165 @@ +{ + "resourceType": "Bundle", + "type": "collection", + "entry": [ + { + "fullUrl": "urn:uuid:119147111821125", + "resource": { + "resourceType": "Patient", + "id": "119147111821125", + "identifier": [ + { + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "MR", + "display": "Medical Record Number" + } + ], + "text": "Medical Record Number" + }, + "system": "http://example.com/system/mrn", + "value": "119147111821125" + } + ], + "name": [ + { + "text": "Archy Marshall", + "family": "Marshall", + "given": [ + "Archy" + ] + } + ], + "gender": "male", + "birthDate": "1994-08-24", + "address": [ + { + "line": [ + "57 Adams St" + ], + "city": "New Rochelle", + "state": "NY", + "postalCode": "10801", + "country": "US" + } + ], + "telecom": [ + { + "system" : "phone", + "value" : "555-555-5555", + "use" : "home" + }, + { + "system" : "email", + "value" : "amy.shaw@example.com" + } + ], + "managingOrganization": { + "reference": "Organization/2.16.840.1.113883.19.5", + "display": "Good Health Clinic" + }, + "generalPractitioner": [ + { + "reference": "Practitioner/example", + "display": "Dr Adam Careful" + } + ], + "contact": [ + { + "relationship": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0131", + "code": "C" + } + ] + } + ], + "name": { + "family": "Chalmers", + "given": [ + "Peter", + "James" + ] + }, + "telecom": [ + { + "system": "phone", + "value": "(03) 5555 6473", + "use": "work" + } + ] + } + ], + "photo": [ + { + "contentType": "image/gif", + "data": "R0lGODlhEwARAPcAAAAAAAAA/+9aAO+1AP/WAP/eAP/eCP/eEP/eGP/nAP/nCP/nEP/nIf/nKf/nUv/nWv/vAP/vCP/vEP/vGP/vIf/vKf/vMf/vOf/vWv/vY//va//vjP/3c//3lP/3nP//tf//vf///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEAAAEALAAAAAATABEAAAi+AAMIDDCgYMGBCBMSvMCQ4QCFCQcwDBGCA4cLDyEGECDxAoAQHjxwyKhQAMeGIUOSJJjRpIAGDS5wCDly4AALFlYOgHlBwwOSNydM0AmzwYGjBi8IHWoTgQYORg8QIGDAwAKhESI8HIDgwQaRDI1WXXAhK9MBBzZ8/XDxQoUFZC9IiCBh6wEHGz6IbNuwQoSpWxEgyLCXL8O/gAnylNlW6AUEBRIL7Og3KwQIiCXb9HsZQoIEUzUjNEiaNMKAAAA7" + } + ], + "link": [ + { + "other": { + "reference": "Patient/pat2" + }, + "type": "seealso" + } + ], + "multipleBirthInteger": 2, + "communication": [ + { + "language": { + "coding": [ + { + "system": "urn:ietf:bcp:47", + "code": "en" + } + ] + } + } + ], + "extension": [ + { + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "code": "1002-5", + "system": "urn:oid:2.16.840.1.113883.6.238" + } + }, + { + "url": "text", + "valueString": "American Indian or Alaska Native" + } + ], + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race" + }, + { + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "code": "2186-5", + "system": "urn:oid:2.16.840.1.113883.6.238" + } + }, + { + "url": "text", + "valueString": "Non Hispanic or Latino" + } + ], + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity" + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex", + "valueCode": "male" + } + ] + } + } + ] +} diff --git a/test/helpers/fixtures/masked-patient-bundle.json b/test/helpers/fixtures/masked-patient-bundle.json index 6e9bb78b..beb49fe1 100644 --- a/test/helpers/fixtures/masked-patient-bundle.json +++ b/test/helpers/fixtures/masked-patient-bundle.json @@ -131,8 +131,74 @@ "valueCode": "masked" } ] - } + }, + "telecom": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", + "valueCode": "masked" + } + ] + } + ], + "_multipleBirthInteger": { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", + "valueCode": "masked" + } + ] + }, + "contact": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", + "valueCode": "masked" + } + ] + } + ], + "generalPractitioner": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", + "valueCode": "masked" + } + ] + } + ], + "managingOrganization": { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", + "valueCode": "masked" + } + ] + }, + "link": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", + "valueCode": "masked" + } + ] + } + ], + "photo": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", + "valueCode": "masked" + } + ] + } + ] } } ] -} \ No newline at end of file +} diff --git a/test/helpers/patientUtils.test.js b/test/helpers/patientUtils.test.js index 288c5a37..abc9481f 100644 --- a/test/helpers/patientUtils.test.js +++ b/test/helpers/patientUtils.test.js @@ -2,7 +2,7 @@ const _ = require('lodash'); const { getEthnicityDisplay, getRaceCodesystem, getRaceDisplay, getPatientName, maskPatientData, } = require('../../src/helpers/patientUtils'); -const examplePatient = require('../extractors/fixtures/csv-patient-bundle.json'); +const examplePatient = require('../extractors/fixtures/extended-patient-bundle.json'); const exampleMaskedPatient = require('./fixtures/masked-patient-bundle.json'); describe('PatientUtils', () => { @@ -92,7 +92,24 @@ describe('PatientUtils', () => { test('bundle should be modified to have dataAbsentReason for all fields specified in mask', () => { const bundle = _.cloneDeep(examplePatient); - maskPatientData(bundle, ['gender', 'mrn', 'name', 'address', 'birthDate', 'language', 'ethnicity', 'birthsex', 'race']); + maskPatientData(bundle, [ + 'gender', + 'mrn', + 'name', + 'address', + 'birthDate', + 'language', + 'ethnicity', + 'birthsex', + 'race', + 'telecom', + 'multipleBirth', + 'photo', + 'contact', + 'generalPractitioner', + 'managingOrganization', + 'link', + ]); expect(bundle).toEqual(exampleMaskedPatient); }); @@ -108,7 +125,24 @@ describe('PatientUtils', () => { }, ], }; - maskPatientData(bundle, ['gender', 'mrn', 'name', 'address', 'birthDate', 'language', 'ethnicity', 'birthsex', 'race']); + maskPatientData(bundle, [ + 'gender', + 'mrn', + 'name', + 'address', + 'birthDate', + 'language', + 'ethnicity', + 'birthsex', + 'race', + 'telecom', + 'multipleBirth', + 'photo', + 'contact', + 'generalPractitioner', + 'managingOrganization', + 'link', + ]); expect(bundle).toEqual(exampleMaskedPatient); });