diff --git a/README.md b/README.md index a8abbb63..f4387829 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,18 @@ To mask a property, provide an array of the properties to mask in the `construct } ``` +Alternatively, providing a string with a value of `all` in the `constructorArgs` of the Patient extractor will mask all of the supported properties listed above. The following configuration can be used to mask all properties of the `Patient` resource, rather than listing each individual property: + +```bash +{ + "label": "patient", + "type": "CSVPatientExtractor", + "constructorArgs": { + "filePath": "./data/patient-information.csv" + "mask": "all" + } +} +``` ### Extraction Date Range The mCODE Extraction Client will extract all data that is provided in the CSV files by default, regardless of any dates associated with each row of data. It is recommended that any required date filtering is performed outside of the scope of this client. diff --git a/src/extractors/CSVPatientExtractor.js b/src/extractors/CSVPatientExtractor.js index 14315f4b..f2e66e8a 100644 --- a/src/extractors/CSVPatientExtractor.js +++ b/src/extractors/CSVPatientExtractor.js @@ -87,8 +87,10 @@ class CSVPatientExtractor extends BaseCSVExtractor { // 3. Generate FHIR Resources const bundle = generateMcodeResources('Patient', packagedPatientData); - // mask fields in the patient data if specified in mask array - if (this.mask.length > 0) maskPatientData(bundle, this.mask); + // mask specified fields in the patient data + if (typeof this.mask === 'string' && this.mask === 'all') { + maskPatientData(bundle, [], true); + } else if (this.mask.length > 0) maskPatientData(bundle, this.mask); return bundle; } } diff --git a/src/extractors/FHIRPatientExtractor.js b/src/extractors/FHIRPatientExtractor.js index 0ac7f262..f56a34e5 100644 --- a/src/extractors/FHIRPatientExtractor.js +++ b/src/extractors/FHIRPatientExtractor.js @@ -18,7 +18,10 @@ class FHIRPatientExtractor extends BaseFHIRExtractor { async get(argumentObject) { const bundle = await super.get(argumentObject); - if (this.mask.length > 0) maskPatientData(bundle, this.mask); + // mask specified fields in the patient data + if (typeof this.mask === 'string' && this.mask === 'all') { + maskPatientData(bundle, [], true); + } else if (this.mask.length > 0) maskPatientData(bundle, this.mask); return bundle; } } diff --git a/src/helpers/configUtils.js b/src/helpers/configUtils.js index e0494cde..486fe7e0 100644 --- a/src/helpers/configUtils.js +++ b/src/helpers/configUtils.js @@ -34,6 +34,10 @@ ajv.addFormat('email-with-name', { return emailRegex.test(email.trim().split(' ').pop()); }, }); +ajv.addFormat('mask-all', { + type: 'string', + validate: (string) => string === 'all', +}); const validator = ajv.addSchema(configSchema, 'config'); diff --git a/src/helpers/patientUtils.js b/src/helpers/patientUtils.js index 34a18226..7224c5c2 100644 --- a/src/helpers/patientUtils.js +++ b/src/helpers/patientUtils.js @@ -81,8 +81,9 @@ function getPatientName(name) { * 'gender','mrn','name','address','birthDate','language','ethnicity','birthsex', * 'race', 'telecom', 'multipleBirth', 'photo', 'contact', 'generalPractitioner', * 'managingOrganization', and 'link' + * @param {Boolean} maskAll indicates that all supported fields should be masked, defaults to false */ -function maskPatientData(bundle, mask) { +function maskPatientData(bundle, mask, maskAll = false) { // get Patient resource from bundle const patient = fhirpath.evaluate( bundle, @@ -109,7 +110,9 @@ function maskPatientData(bundle, mask) { ]; const masked = extensionArr(dataAbsentReasonExtension('masked')); - mask.forEach((field) => { + const maskingFields = maskAll ? validFields : mask; + + maskingFields.forEach((field) => { if (!validFields.includes(field)) { throw Error(`'${field}' is not a field that can be masked. Patient will only be extracted if all mask fields are valid. Valid fields include: Valid fields include: ${validFields.join(', ')}`); } diff --git a/src/helpers/schemas/config.schema.json b/src/helpers/schemas/config.schema.json index be659c9c..d942b482 100644 --- a/src/helpers/schemas/config.schema.json +++ b/src/helpers/schemas/config.schema.json @@ -128,10 +128,18 @@ }, "mask": { "title": "Masked Fields", - "type": "array", - "items": { - "type": "string" - } + "oneOf": [ + { + "type": "string", + "format": "mask-all" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] } } } diff --git a/test/helpers/patientUtils.test.js b/test/helpers/patientUtils.test.js index abc9481f..ecd5174e 100644 --- a/test/helpers/patientUtils.test.js +++ b/test/helpers/patientUtils.test.js @@ -113,6 +113,12 @@ describe('PatientUtils', () => { expect(bundle).toEqual(exampleMaskedPatient); }); + test('bundle should be modified to have dataAbsentReason for all fields when the maskAll flag is provided', () => { + const bundle = _.cloneDeep(examplePatient); + maskPatientData(bundle, [], true); + expect(bundle).toEqual(exampleMaskedPatient); + }); + test('should mask gender even if it only had an extension', () => { const bundle = _.cloneDeep(examplePatient); delete bundle.entry[0].resource.gender;