Skip to content

Commit bd77a00

Browse files
authored
Merge pull request #155 from mcode/new-masking-fields
New options for Patient masking
2 parents 4075b87 + 713e4ce commit bd77a00

File tree

5 files changed

+318
-8
lines changed

5 files changed

+318
-8
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ npm start -- --entries-filter --from-date YYYY-MM-DD --to-date YYY-MM-DD --run-l
134134
### Masking Patient Data
135135

136136
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`.
137-
Patient properties that can be masked are: `gender`, `mrn`, `name`, `address`, `birthDate`, `language`, `ethnicity`, `birthsex`, and `race`.
137+
Patient properties that can be masked are: `gender`, `mrn`, `name`, `address`, `birthDate`, `language`, `ethnicity`, `birthsex`, `race`, `telecom`, `multipleBirth`, `photo`, `contact`, `generalPractitioner`, `managingOrganization`, and `link`.
138138
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`:
139139

140140
```bash

src/helpers/patientUtils.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ function getPatientName(name) {
7878
* dataAbsentReason extension with value 'masked'
7979
* @param {Object} bundle a FHIR bundle with a Patient resource
8080
* @param {Array} mask an array of fields to mask. Values can be:
81-
* ['gender','mrn','name','address','birthDate','language','ethnicity','birthsex','race']
81+
* 'gender','mrn','name','address','birthDate','language','ethnicity','birthsex',
82+
* 'race', 'telecom', 'multipleBirth', 'photo', 'contact', 'generalPractitioner',
83+
* 'managingOrganization', and 'link'
8284
*/
8385
function maskPatientData(bundle, mask) {
8486
// get Patient resource from bundle
@@ -87,7 +89,24 @@ function maskPatientData(bundle, mask) {
8789
'Bundle.entry.where(resource.resourceType=\'Patient\').resource,first()',
8890
)[0];
8991

90-
const validFields = ['gender', 'mrn', 'name', 'address', 'birthDate', 'language', 'ethnicity', 'birthsex', 'race'];
92+
const validFields = [
93+
'gender',
94+
'mrn',
95+
'name',
96+
'address',
97+
'birthDate',
98+
'language',
99+
'ethnicity',
100+
'birthsex',
101+
'race',
102+
'telecom',
103+
'multipleBirth',
104+
'photo',
105+
'contact',
106+
'generalPractitioner',
107+
'managingOrganization',
108+
'link',
109+
];
91110
const masked = extensionArr(dataAbsentReasonExtension('masked'));
92111

93112
mask.forEach((field) => {
@@ -157,6 +176,32 @@ function maskPatientData(bundle, mask) {
157176
delete ethnicity[0].extension[1].valueString;
158177
ethnicity[0].extension[1]._valueString = masked;
159178
}
179+
} else if (field === 'telecom' && 'telecom' in patient) {
180+
delete patient.telecom;
181+
patient.telecom = [masked];
182+
} else if (field === 'multipleBirth') {
183+
if ('multipleBirthBoolean' in patient) {
184+
delete patient.multipleBirthBoolean;
185+
patient._multipleBirthBoolean = masked;
186+
} else if ('multipleBirthInteger' in patient) {
187+
delete patient.multipleBirthInteger;
188+
patient._multipleBirthInteger = masked;
189+
}
190+
} else if (field === 'photo' && 'photo' in patient) {
191+
delete patient.photo;
192+
patient.photo = [masked];
193+
} else if (field === 'contact' && 'contact' in patient) {
194+
delete patient.contact;
195+
patient.contact = [masked];
196+
} else if (field === 'generalPractitioner' && 'generalPractitioner' in patient) {
197+
delete patient.generalPractitioner;
198+
patient.generalPractitioner = [masked];
199+
} else if (field === 'managingOrganization' && 'managingOrganization' in patient) {
200+
delete patient.managingOrganization;
201+
patient.managingOrganization = masked;
202+
} else if (field === 'link' && 'link' in patient) {
203+
delete patient.link;
204+
patient.link = [masked];
160205
}
161206
});
162207
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
{
2+
"resourceType": "Bundle",
3+
"type": "collection",
4+
"entry": [
5+
{
6+
"fullUrl": "urn:uuid:119147111821125",
7+
"resource": {
8+
"resourceType": "Patient",
9+
"id": "119147111821125",
10+
"identifier": [
11+
{
12+
"type": {
13+
"coding": [
14+
{
15+
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
16+
"code": "MR",
17+
"display": "Medical Record Number"
18+
}
19+
],
20+
"text": "Medical Record Number"
21+
},
22+
"system": "http://example.com/system/mrn",
23+
"value": "119147111821125"
24+
}
25+
],
26+
"name": [
27+
{
28+
"text": "Archy Marshall",
29+
"family": "Marshall",
30+
"given": [
31+
"Archy"
32+
]
33+
}
34+
],
35+
"gender": "male",
36+
"birthDate": "1994-08-24",
37+
"address": [
38+
{
39+
"line": [
40+
"57 Adams St"
41+
],
42+
"city": "New Rochelle",
43+
"state": "NY",
44+
"postalCode": "10801",
45+
"country": "US"
46+
}
47+
],
48+
"telecom": [
49+
{
50+
"system" : "phone",
51+
"value" : "555-555-5555",
52+
"use" : "home"
53+
},
54+
{
55+
"system" : "email",
56+
"value" : "amy.shaw@example.com"
57+
}
58+
],
59+
"managingOrganization": {
60+
"reference": "Organization/2.16.840.1.113883.19.5",
61+
"display": "Good Health Clinic"
62+
},
63+
"generalPractitioner": [
64+
{
65+
"reference": "Practitioner/example",
66+
"display": "Dr Adam Careful"
67+
}
68+
],
69+
"contact": [
70+
{
71+
"relationship": [
72+
{
73+
"coding": [
74+
{
75+
"system": "http://terminology.hl7.org/CodeSystem/v2-0131",
76+
"code": "C"
77+
}
78+
]
79+
}
80+
],
81+
"name": {
82+
"family": "Chalmers",
83+
"given": [
84+
"Peter",
85+
"James"
86+
]
87+
},
88+
"telecom": [
89+
{
90+
"system": "phone",
91+
"value": "(03) 5555 6473",
92+
"use": "work"
93+
}
94+
]
95+
}
96+
],
97+
"photo": [
98+
{
99+
"contentType": "image/gif",
100+
"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"
101+
}
102+
],
103+
"link": [
104+
{
105+
"other": {
106+
"reference": "Patient/pat2"
107+
},
108+
"type": "seealso"
109+
}
110+
],
111+
"multipleBirthInteger": 2,
112+
"communication": [
113+
{
114+
"language": {
115+
"coding": [
116+
{
117+
"system": "urn:ietf:bcp:47",
118+
"code": "en"
119+
}
120+
]
121+
}
122+
}
123+
],
124+
"extension": [
125+
{
126+
"extension": [
127+
{
128+
"url": "ombCategory",
129+
"valueCoding": {
130+
"code": "1002-5",
131+
"system": "urn:oid:2.16.840.1.113883.6.238"
132+
}
133+
},
134+
{
135+
"url": "text",
136+
"valueString": "American Indian or Alaska Native"
137+
}
138+
],
139+
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"
140+
},
141+
{
142+
"extension": [
143+
{
144+
"url": "ombCategory",
145+
"valueCoding": {
146+
"code": "2186-5",
147+
"system": "urn:oid:2.16.840.1.113883.6.238"
148+
}
149+
},
150+
{
151+
"url": "text",
152+
"valueString": "Non Hispanic or Latino"
153+
}
154+
],
155+
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"
156+
},
157+
{
158+
"url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex",
159+
"valueCode": "male"
160+
}
161+
]
162+
}
163+
}
164+
]
165+
}

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

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,74 @@
131131
"valueCode": "masked"
132132
}
133133
]
134-
}
134+
},
135+
"telecom": [
136+
{
137+
"extension": [
138+
{
139+
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
140+
"valueCode": "masked"
141+
}
142+
]
143+
}
144+
],
145+
"_multipleBirthInteger": {
146+
"extension": [
147+
{
148+
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
149+
"valueCode": "masked"
150+
}
151+
]
152+
},
153+
"contact": [
154+
{
155+
"extension": [
156+
{
157+
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
158+
"valueCode": "masked"
159+
}
160+
]
161+
}
162+
],
163+
"generalPractitioner": [
164+
{
165+
"extension": [
166+
{
167+
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
168+
"valueCode": "masked"
169+
}
170+
]
171+
}
172+
],
173+
"managingOrganization": {
174+
"extension": [
175+
{
176+
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
177+
"valueCode": "masked"
178+
}
179+
]
180+
},
181+
"link": [
182+
{
183+
"extension": [
184+
{
185+
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
186+
"valueCode": "masked"
187+
}
188+
]
189+
}
190+
],
191+
"photo": [
192+
{
193+
"extension": [
194+
{
195+
"url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
196+
"valueCode": "masked"
197+
}
198+
]
199+
}
200+
]
135201
}
136202
}
137203
]
138-
}
204+
}

test/helpers/patientUtils.test.js

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const _ = require('lodash');
22
const {
33
getEthnicityDisplay, getRaceCodesystem, getRaceDisplay, getPatientName, maskPatientData,
44
} = require('../../src/helpers/patientUtils');
5-
const examplePatient = require('../extractors/fixtures/csv-patient-bundle.json');
5+
const examplePatient = require('../extractors/fixtures/extended-patient-bundle.json');
66
const exampleMaskedPatient = require('./fixtures/masked-patient-bundle.json');
77

88
describe('PatientUtils', () => {
@@ -92,7 +92,24 @@ describe('PatientUtils', () => {
9292

9393
test('bundle should be modified to have dataAbsentReason for all fields specified in mask', () => {
9494
const bundle = _.cloneDeep(examplePatient);
95-
maskPatientData(bundle, ['gender', 'mrn', 'name', 'address', 'birthDate', 'language', 'ethnicity', 'birthsex', 'race']);
95+
maskPatientData(bundle, [
96+
'gender',
97+
'mrn',
98+
'name',
99+
'address',
100+
'birthDate',
101+
'language',
102+
'ethnicity',
103+
'birthsex',
104+
'race',
105+
'telecom',
106+
'multipleBirth',
107+
'photo',
108+
'contact',
109+
'generalPractitioner',
110+
'managingOrganization',
111+
'link',
112+
]);
96113
expect(bundle).toEqual(exampleMaskedPatient);
97114
});
98115

@@ -108,7 +125,24 @@ describe('PatientUtils', () => {
108125
},
109126
],
110127
};
111-
maskPatientData(bundle, ['gender', 'mrn', 'name', 'address', 'birthDate', 'language', 'ethnicity', 'birthsex', 'race']);
128+
maskPatientData(bundle, [
129+
'gender',
130+
'mrn',
131+
'name',
132+
'address',
133+
'birthDate',
134+
'language',
135+
'ethnicity',
136+
'birthsex',
137+
'race',
138+
'telecom',
139+
'multipleBirth',
140+
'photo',
141+
'contact',
142+
'generalPractitioner',
143+
'managingOrganization',
144+
'link',
145+
]);
112146
expect(bundle).toEqual(exampleMaskedPatient);
113147
});
114148

0 commit comments

Comments
 (0)