From 017699006460fe30ef67d1fe49f3b9e304edd5df Mon Sep 17 00:00:00 2001 From: Julia Afeltra Date: Mon, 12 Apr 2021 16:50:54 -0400 Subject: [PATCH 1/2] Don't require name or gender in CSV. Mark as dataAbsentReason unknown if missing --- src/extractors/CSVPatientExtractor.js | 4 +-- src/helpers/patientUtils.js | 9 +++-- src/helpers/schemas/csv.js | 6 ++-- src/templates/PatientTemplate.js | 34 +++++++++++++++---- test/helpers/patientUtils.test.js | 16 +++++++++ test/templates/fixtures/patient-resource.json | 20 ++++++++--- test/templates/patient.test.js | 6 ++-- 7 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/extractors/CSVPatientExtractor.js b/src/extractors/CSVPatientExtractor.js index 14b71992..b1fb39fb 100644 --- a/src/extractors/CSVPatientExtractor.js +++ b/src/extractors/CSVPatientExtractor.js @@ -14,8 +14,8 @@ function joinAndReformatData(patientData) { mrn, familyName, givenName, gender, birthsex, dateOfBirth, race, ethnicity, language, addressLine, city, state, zip, } = patientData; - if (!mrn || !familyName || !givenName || !gender) { - throw Error('Missing required field for Patient CSV Extraction. Required values include: mrn, familyName, givenName, gender'); + if (!mrn) { + throw Error('Missing required field for Patient CSV Extraction: mrn'); } return { diff --git a/src/helpers/patientUtils.js b/src/helpers/patientUtils.js index 47eaded2..6cb37721 100644 --- a/src/helpers/patientUtils.js +++ b/src/helpers/patientUtils.js @@ -99,6 +99,9 @@ function maskPatientData(bundle, mask) { delete patient.gender; // an underscore is added when a primitive type is being replaced by an object (extension) patient._gender = masked; + } else if (field === 'gender' && '_gender' in patient) { + 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) { patient.identifier = [masked]; } else if (field === 'name' && 'name' in patient) { @@ -120,7 +123,7 @@ function maskPatientData(bundle, mask) { ); // fhirpath.evaluate will return [] if there is no extension with the given URL // so checking if the result is [] checks if the field exists to be masked - if (birthsex !== []) { + if (birthsex.length > 0) { delete birthsex[0].valueCode; birthsex[0]._valueCode = masked; } @@ -129,7 +132,7 @@ function maskPatientData(bundle, mask) { patient, 'Patient.extension.where(url=\'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race\')', ); - if (race !== []) { + if (race.length > 0) { race[0].extension[0].valueCoding = masked; delete race[0].extension[1].valueString; race[0].extension[1]._valueString = masked; @@ -139,7 +142,7 @@ function maskPatientData(bundle, mask) { patient, 'Patient.extension.where(url=\'http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity\')', ); - if (ethnicity !== []) { + if (ethnicity.length > 0) { ethnicity[0].extension[0].valueCoding = masked; delete ethnicity[0].extension[1].valueString; ethnicity[0].extension[1]._valueString = masked; diff --git a/src/helpers/schemas/csv.js b/src/helpers/schemas/csv.js index be0a42c4..f7a9f4b0 100644 --- a/src/helpers/schemas/csv.js +++ b/src/helpers/schemas/csv.js @@ -31,9 +31,9 @@ const CSVConditionSchema = { const CSVPatientSchema = { headers: [ { name: 'mrn', required: true }, - { name: 'familyName', required: true }, - { name: 'givenName', required: true }, - { name: 'gender', required: true }, + { name: 'familyName' }, + { name: 'givenName' }, + { name: 'gender' }, { name: 'birthsex' }, { name: 'dateOfBirth' }, { name: 'race' }, diff --git a/src/templates/PatientTemplate.js b/src/templates/PatientTemplate.js index 719e155e..a6eadceb 100644 --- a/src/templates/PatientTemplate.js +++ b/src/templates/PatientTemplate.js @@ -1,4 +1,4 @@ -const { extensionArr, coding, valueX } = require('./snippets'); +const { dataAbsentReasonExtension, extensionArr, coding, valueX } = require('./snippets'); const { ifAllArgsObj, ifSomeArgsObj } = require('../helpers/templateUtils'); function mrnIdentifierTemplate({ mrn }) { @@ -73,13 +73,35 @@ function birthDateTemplate({ dateOfBirth }) { }; } +function genderTemplate({ gender }) { + if (!gender) { + // gender is 1..1 in mCODE + return { + _gender: extensionArr(dataAbsentReasonExtension('unknown')), + }; + } + + return { + gender, + }; +} + function nameTemplate({ familyName, givenName }) { + if (!familyName && !givenName) { + // name is 1..* in mCODE + return { + name: [ + extensionArr(dataAbsentReasonExtension('unknown')), + ], + }; + } + return { name: [ { text: `${givenName} ${familyName}`, - family: familyName, - given: givenName.split(' '), + ...(familyName && { family: familyName }), + ...(givenName && { given: givenName.split(' ') }), }, ], }; @@ -125,13 +147,13 @@ function languageTemplate({ language }) { function patientTemplate({ id, mrn, familyName, givenName, gender, birthsex, dateOfBirth, language, addressLine, city, state, zip, raceCodesystem, raceCode, raceText, ethnicityCode, ethnicityText, }) { - if (!(id && mrn && familyName && givenName && gender)) { - throw Error('Trying to render a PatientTemplate, but a required argument is missing; ensure that id, mrn, familyName, givenName, and gender are all present'); + if (!(id && mrn)) { + throw Error('Trying to render a PatientTemplate, but a required argument is missing; ensure that id and mrn are both present'); } return { resourceType: 'Patient', id, - gender, + ...genderTemplate({ gender }), ...mrnIdentifierTemplate({ mrn }), ...nameTemplate({ familyName, givenName }), ...ifSomeArgsObj(addressTemplate)({ addressLine, city, state, zip }), diff --git a/test/helpers/patientUtils.test.js b/test/helpers/patientUtils.test.js index 201dc325..6cb33c29 100644 --- a/test/helpers/patientUtils.test.js +++ b/test/helpers/patientUtils.test.js @@ -98,6 +98,22 @@ describe('PatientUtils', () => { 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; + // eslint-disable-next-line no-underscore-dangle + bundle.entry[0].resource._gender = { + extension: [ + { + url: 'http://hl7.org/fhir/StructureDefinition/data-absent-reason', + valueCode: 'unknown', + }, + ], + }; + maskPatientData(bundle, ['gender', 'mrn', 'name', 'address', 'birthDate', 'language', 'ethnicity', 'birthsex', 'race']); + expect(bundle).toEqual(exampleMaskedPatient); + }); + test('should throw error when provided an invalid field to mask', () => { const bundle = _.cloneDeep(examplePatient); expect(() => maskPatientData(bundle, ['this is an invalid field', 'mrn'])).toThrowError(); diff --git a/test/templates/fixtures/patient-resource.json b/test/templates/fixtures/patient-resource.json index 7a78f0e6..5003267b 100644 --- a/test/templates/fixtures/patient-resource.json +++ b/test/templates/fixtures/patient-resource.json @@ -18,11 +18,21 @@ } ], "name": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", + "valueCode": "unknown" + } + ] + } + ], + "_gender": { + "extension": [ { - "text": "Test Patient", - "family": "Patient", - "given": [ "Test" ] + "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason", + "valueCode": "unknown" } - ], - "gender": "female" + ] + } } diff --git a/test/templates/patient.test.js b/test/templates/patient.test.js index 0cc9a097..7f8615db 100644 --- a/test/templates/patient.test.js +++ b/test/templates/patient.test.js @@ -9,9 +9,9 @@ describe('JavaScript Render Patient', () => { const PATIENT_VALID_DATA = { id: 'SomeId', mrn: '1234', - familyName: 'Patient', - givenName: 'Test', - gender: 'female', + familyName: null, + givenName: null, + gender: null, birthsex: null, dateOfBirth: null, language: null, From 235d66bd8564d04108faef1f5c5b501c04a22ddb Mon Sep 17 00:00:00 2001 From: Julia Afeltra Date: Tue, 13 Apr 2021 09:01:52 -0400 Subject: [PATCH 2/2] Update comment --- src/helpers/patientUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/patientUtils.js b/src/helpers/patientUtils.js index 6cb37721..babd76d6 100644 --- a/src/helpers/patientUtils.js +++ b/src/helpers/patientUtils.js @@ -122,7 +122,7 @@ function maskPatientData(bundle, mask) { 'Patient.extension.where(url=\'http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex\')', ); // fhirpath.evaluate will return [] if there is no extension with the given URL - // so checking if the result is [] checks if the field exists to be masked + // so checking if the result is an array with anything in it checks if the field exists to be masked if (birthsex.length > 0) { delete birthsex[0].valueCode; birthsex[0]._valueCode = masked;