@andes/fhir 1.11.1 → 1.12.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.circleci/config.yml +71 -68
  2. package/.editorconfig +13 -13
  3. package/.env.example +2 -0
  4. package/.github/labeler.yml +10 -10
  5. package/.github/pull_request_template.md +28 -28
  6. package/.github/workflows/build_test.yml +14 -14
  7. package/.github/workflows/has_conflicts.yml +13 -13
  8. package/.vscode/settings.json +2 -0
  9. package/CHANGELOG.md +312 -291
  10. package/LICENSE +674 -674
  11. package/README.md +2 -2
  12. package/coverage/clover.xml +198 -0
  13. package/coverage/coverage-final.json +5 -0
  14. package/coverage/lcov-report/base.css +224 -0
  15. package/coverage/lcov-report/block-navigation.js +87 -0
  16. package/coverage/lcov-report/config.ts.html +136 -0
  17. package/coverage/lcov-report/device.ts.html +193 -0
  18. package/coverage/lcov-report/favicon.png +0 -0
  19. package/coverage/lcov-report/helpers/index.html +116 -0
  20. package/coverage/lcov-report/helpers/patientFactory.ts.html +124 -0
  21. package/coverage/lcov-report/index.html +131 -0
  22. package/coverage/lcov-report/lib/config.ts.html +136 -0
  23. package/coverage/lcov-report/lib/device.ts.html +193 -0
  24. package/coverage/lcov-report/lib/index.html +146 -0
  25. package/coverage/lcov-report/lib/patient.ts.html +1267 -0
  26. package/coverage/lcov-report/patient.ts.html +1207 -0
  27. package/coverage/lcov-report/prettify.css +1 -0
  28. package/coverage/lcov-report/prettify.js +2 -0
  29. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  30. package/coverage/lcov-report/sorter.js +210 -0
  31. package/coverage/lcov.info +410 -0
  32. package/dist/index.js +51 -32
  33. package/dist/index.js.map +1 -1
  34. package/dist/src/data/patient.json +191 -185
  35. package/dist/src/helpers/patientFactory.js +31 -0
  36. package/dist/src/helpers/patientFactory.js.map +1 -0
  37. package/dist/src/lib/allergyIntolerance.js +39 -39
  38. package/dist/src/lib/allergyIntolerance.js.map +1 -1
  39. package/dist/src/lib/bundle.js +25 -22
  40. package/dist/src/lib/bundle.js.map +1 -1
  41. package/dist/src/lib/composition.js +151 -150
  42. package/dist/src/lib/composition.js.map +1 -1
  43. package/dist/src/lib/condition.js +79 -79
  44. package/dist/src/lib/condition.js.map +1 -1
  45. package/dist/src/lib/config.js +20 -22
  46. package/dist/src/lib/config.js.map +1 -1
  47. package/dist/src/lib/config.test.js +12 -12
  48. package/dist/src/lib/config.test.js.map +1 -1
  49. package/dist/src/lib/device.js +38 -38
  50. package/dist/src/lib/device.js.map +1 -1
  51. package/dist/src/lib/device.test.js +9 -9
  52. package/dist/src/lib/device.test.js.map +1 -1
  53. package/dist/src/lib/document-reference.js +106 -103
  54. package/dist/src/lib/document-reference.js.map +1 -1
  55. package/dist/src/lib/immunization.js +67 -67
  56. package/dist/src/lib/medication.js +64 -64
  57. package/dist/src/lib/medication.js.map +1 -1
  58. package/dist/src/lib/medicationRequest.js +102 -99
  59. package/dist/src/lib/medicationRequest.js.map +1 -1
  60. package/dist/src/lib/medicationStatement.js +55 -55
  61. package/dist/src/lib/medicationStatement.js.map +1 -1
  62. package/dist/src/lib/organization.js +90 -90
  63. package/dist/src/lib/organization.js.map +1 -1
  64. package/dist/src/lib/patient.js +378 -360
  65. package/dist/src/lib/patient.js.map +1 -1
  66. package/dist/src/lib/patient.test.js +78 -39
  67. package/dist/src/lib/patient.test.js.map +1 -1
  68. package/dist/src/lib/practitioner.js +160 -160
  69. package/dist/src/lib/practitioner.js.map +1 -1
  70. package/index.ts +31 -31
  71. package/jest.config.js +11 -11
  72. package/package.json +45 -54
  73. package/release.config.js +74 -0
  74. package/src/data/patient.json +190 -184
  75. package/src/helpers/patientFactory.ts +13 -0
  76. package/src/lib/allergyIntolerance.ts +36 -36
  77. package/src/lib/bundle.ts +19 -19
  78. package/src/lib/composition.ts +153 -153
  79. package/src/lib/condition.ts +80 -80
  80. package/src/lib/config.test.ts +13 -13
  81. package/src/lib/config.ts +17 -17
  82. package/src/lib/device.test.ts +9 -9
  83. package/src/lib/device.ts +36 -36
  84. package/src/lib/document-reference.ts +102 -102
  85. package/src/lib/immunization.ts +64 -64
  86. package/src/lib/medication.ts +61 -61
  87. package/src/lib/medicationRequest.ts +100 -100
  88. package/src/lib/medicationStatement.ts +52 -52
  89. package/src/lib/organization.ts +89 -89
  90. package/src/lib/patient.test.ts +62 -38
  91. package/src/lib/patient.ts +394 -374
  92. package/src/lib/practitioner.ts +157 -157
  93. package/tsconfig.test.json +17 -17
  94. package/tslint.json +138 -138
@@ -1,374 +1,394 @@
1
-
2
- import { getDominio, makeUrl } from './config';
3
-
4
- /**
5
- * Encode a patient from ANDES to FHIR
6
- * @param {} patient
7
- */
8
- export function encode(patient) {
9
- if (patient) {
10
- const identificadores: any = [];
11
- if (patient.documento) {
12
- identificadores.push({
13
- system: 'http://www.renaper.gob.ar/dni',
14
- value: patient.documento
15
- });
16
- }
17
- if (patient.cuil) {
18
- identificadores.push({
19
- system: 'http://www.renaper.gob.ar/cuil',
20
- value: patient.cuil
21
- });
22
- }
23
- identificadores.push({
24
- system: getDominio(),
25
- value: patient._id
26
- });
27
- // Parsea contactos
28
- let contactos = patient.contacto ? patient.contacto.filter(c => c.valor).map(unContacto => {
29
- let cont = {
30
- resourceType: 'ContactPoint',
31
- value: unContacto.valor,
32
- rank: unContacto.ranking,
33
- };
34
- switch (unContacto.tipo) {
35
- case 'fijo':
36
- cont['system'] = 'phone';
37
- break;
38
- case 'celular':
39
- cont['system'] = 'phone';
40
- break;
41
- case 'email':
42
- cont['system'] = 'email';
43
- break;
44
- }
45
- return cont;
46
- }) : [];
47
- // Parsea direcciones
48
- let direcciones = patient.direccion ? patient.direccion.filter(dir => dir.ubicacion.localidad).map(unaDireccion => {
49
- let direc = {
50
- resourceType: 'Address',
51
- postalCode: unaDireccion.codigoPostal ? unaDireccion.codigoPostal : '',
52
- line: [unaDireccion.valor],
53
- city: unaDireccion.ubicacion.localidad ? unaDireccion.ubicacion.localidad.nombre : '',
54
- state: unaDireccion.ubicacion.provincia ? unaDireccion.ubicacion.provincia.nombre : '',
55
- country: unaDireccion.ubicacion.pais ? unaDireccion.ubicacion.pais.nombre : '',
56
- };
57
- return direc;
58
- }) : [];
59
- // Parsea relaciones
60
- let relaciones = patient.relaciones ? patient.relaciones.filter(r => !!r.relacion).map(unaRelacion => {
61
- let relacion = {
62
- relationship: [{
63
- text: unaRelacion.relacion.nombre
64
- }], // The kind of relationship
65
- name: {
66
- resourceType: 'HumanName',
67
- family: unaRelacion.apellido.split(' '), // Family name (often called 'Surname')
68
- given: unaRelacion.nombre.split(' '), // Given names (not always 'first'). Includes middle names
69
- }
70
- };
71
- return relacion;
72
- }) : [];
73
- let genero;
74
- switch (patient.genero) {
75
- case 'femenino':
76
- genero = 'female';
77
- break;
78
- case 'masculino':
79
- genero = 'male';
80
- break;
81
- case 'otro':
82
- genero = 'other';
83
- break;
84
- }
85
- let pacienteFHIR: any = {
86
- id: patient.id,
87
- resourceType: 'Patient',
88
- identifier: identificadores,
89
- active: patient.activo ? patient.activo : null, // Whether this patient's record is in active use
90
- name: [{
91
- use: 'official',
92
- resourceType: 'HumanName',
93
- family: patient.apellido.split(' '),
94
- given: patient.nombre.split(' '),
95
- text: `${patient.nombre} ${patient.apellido}`,
96
- _family: [{
97
- extension: [
98
- {
99
- url: 'http://hl7.org/fhir/StructureDefinition/humanname-fathers-family',
100
- valueString: patient.apellido
101
- },
102
- ]
103
- }],
104
- }],
105
- gender: genero, // male | female | other | unknown
106
- birthDate: patient.fechaNacimiento ? typeof patient.fechaNacimiento === 'string' ? new Date(patient.fechaNacimiento).toISOString().slice(0, 10) : patient.fechaNacimiento.toISOString().slice(0, 10) : null
107
- };
108
- if (patient.fechaFallecimiento) {
109
- pacienteFHIR.deceasedDateTime = typeof patient.fechaFallecimiento === 'string' ? new Date(patient.fechaFallecimiento.toISOString().slice(0, 10)) : patient.fechaFallecimiento.toISOString().slice(0, 10);
110
- }
111
- if (patient.estadoCivil) {
112
- let estadoCivil;
113
- switch (patient.estadoCivil) {
114
- case 'casado':
115
- estadoCivil = 'Married';
116
- break;
117
- case 'divorciado':
118
- estadoCivil = 'Divorced';
119
- break;
120
- case 'viudo':
121
- estadoCivil = 'Widowed';
122
- break;
123
- case 'soltero':
124
- estadoCivil = 'unmarried';
125
- break;
126
- default:
127
- estadoCivil = 'unknown';
128
- break;
129
- }
130
- pacienteFHIR['maritalStatus'] = {
131
- text: estadoCivil
132
- };
133
- }
134
- if (patient.foto) {
135
- pacienteFHIR['photo'] = [{
136
- patient: patient.foto
137
- }];
138
- }
139
- if (contactos.length > 0) { // A contact detail for the individual
140
- pacienteFHIR['telecom'] = contactos;
141
- }
142
- if (direcciones.length > 0) { // Addresses for the individual
143
- pacienteFHIR['address'] = direcciones;
144
- }
145
- if (relaciones.length > 0) { // A contact party (e.g. guardian, partner, friend) for the patient
146
- pacienteFHIR['contact'] = relaciones;
147
- }
148
- return pacienteFHIR;
149
- } else {
150
- return null;
151
- }
152
- }
153
-
154
- /**
155
- * Decode a patient from FHIR to ANDES
156
- * @param {} patient
157
- */
158
- export function decode(patient) {
159
- let genero;
160
- let sexo;
161
- // Cuando el paciente viene por FHIR suponemos el valor del genero tanto para nuestro field genero como sexo
162
- switch (patient.gender) {
163
- case 'female':
164
- genero = 'femenino';
165
- sexo = 'femenino';
166
- break;
167
- case 'male':
168
- genero = 'masculino';
169
- sexo = 'masculino';
170
- break;
171
- case 'other':
172
- genero = 'otro';
173
- sexo = 'otro';
174
- break;
175
- }
176
- function getValue(items, key) {
177
- const element = items.find(el => el.system === key);
178
- if (element) {
179
- return element.value;
180
- }
181
- }
182
- let pacienteAndes = {
183
- id: getValue(patient.identifier, makeUrl('Patient')),
184
- documento: getValue(patient.identifier, 'http://www.renaper.gob.ar/dni'),
185
- nombre: patient.name[0].given.join().replace(',', ' '),
186
- apellido: Array.isArray(patient.name[0].family) ? patient.name[0].family.join().replace(',', ' ') : patient.name[0].family,
187
- fechaNacimiento: patient.birthDate,
188
- genero,
189
- sexo,
190
- estado: 'temporal' // Todos los pacientes que recibimos por Fhir son considerados temporales en su conversión.
191
- };
192
- let contactos = patient.telecom ? patient.telecom.map(unContacto => {
193
- let cont = {
194
- valor: unContacto.value,
195
- ranking: unContacto.rank
196
- };
197
- switch (unContacto.system) {
198
- case 'phone':
199
- cont['tipo'] = 'celular';
200
- break;
201
- case 'email':
202
- cont['tipo'] = 'email';
203
- break;
204
- }
205
- return cont;
206
- }) : [];
207
-
208
- let relaciones = patient.contact ? patient.contact.map(aContact => {
209
- let relacion = {
210
- relacion: {
211
- nombre: aContact.relationship[0].text
212
- },
213
- nombre: aContact.name.given.join().replace(',', ' '),
214
- apellido: aContact.name.family
215
- };
216
- return relacion;
217
- }) : [];
218
-
219
- if (patient.maritalStatus) {
220
- let estadoCivil;
221
- switch (patient.maritalStatus['text']) {
222
- case 'Married':
223
- estadoCivil = 'casado';
224
- break;
225
- case 'Divorced':
226
- estadoCivil = 'divorciado';
227
- break;
228
- case 'Widowed':
229
- estadoCivil = 'viudo';
230
- break;
231
- case 'unmarried':
232
- estadoCivil = 'soltero';
233
- break;
234
- default:
235
- estadoCivil = 'otro';
236
- break;
237
- }
238
- pacienteAndes['estadoCivil'] = estadoCivil;
239
- }
240
-
241
- let direcciones = patient.address ? patient.address.map(unaDireccion => {
242
- let dir = {
243
- activo: true,
244
- valor: unaDireccion.line,
245
- codigoPostal: unaDireccion.postalCode,
246
- // TODO: En un MATETIME ver si la información de pais, provincia, localidad
247
- // se delega en el insert o update. Y este decoder sólo guarda la info tal cual viene.
248
- ubicacion: {
249
- pais: unaDireccion.country,
250
- provincia: unaDireccion.state,
251
- localidad: unaDireccion.city
252
- }
253
- };
254
- return dir;
255
- }) : [];
256
-
257
- pacienteAndes['direccion'] = direcciones;
258
-
259
- if (patient.active) {
260
- pacienteAndes['activo'] = patient.active;
261
- }
262
- if (contactos.length > 0) {
263
- pacienteAndes['contacto'] = contactos;
264
- }
265
- if (patient.photo) { // Image of the patient
266
- pacienteAndes['foto'] = patient.photo[0].data;
267
- }
268
-
269
- if (patient.contact) {
270
- pacienteAndes['relaciones'] = relaciones;
271
- }
272
-
273
- return pacienteAndes;
274
- }
275
- /**
276
- * Verify if a patient has a FHIR format
277
- * @param {*} patient
278
- */
279
- export function verify(patient) {
280
- let respuesta = true;
281
- let fieldVerified = Object.keys(patient).every(pacienteFHIRFields);
282
- // Verificamos que las key esten contenidas en los conjuntos mínimos pacienteFHIRField
283
- if (fieldVerified) {
284
- respuesta = ('resourceType' in patient) && patient.resourceType === 'Patient';
285
- respuesta = respuesta && ('identifier' in patient);
286
- if (patient.identifier.length > 0) {
287
- patient.identifier.forEach(anIdentifier => respuesta = respuesta && Object.keys(anIdentifier).every(identifierFields));
288
-
289
- }
290
- patient.name.forEach(aName => respuesta = respuesta && validName(aName));
291
- if (patient.active) {
292
- respuesta = respuesta && (typeof patient.active === 'boolean');
293
- }
294
- if (patient.telecom) {
295
- patient.telecom.forEach(aTelecom => respuesta = respuesta && Object.keys(aTelecom).every(telecomFields));
296
- }
297
- if (patient.gender) {
298
- respuesta = respuesta && patient.gender.match('male|female|other|unknown') != null;
299
- }
300
- if (patient.birthDate) {
301
- respuesta = respuesta && typeof patient.birthDate === 'string'; // TODO: Algun control de que tenga formato Date
302
- }
303
- if (patient.deceasedDateTime) {
304
- respuesta = respuesta && typeof patient.deceasedDateTime === 'string'; // TODO: Algun control de que tenga formato DateTime
305
- }
306
- if (patient.address) {
307
- patient.address.forEach(anAddress => respuesta = respuesta && Object.keys(anAddress).every(addressFields));
308
- }
309
- if (patient.maritalStatus && patient.maritalStatus.text) {
310
- respuesta = respuesta && Object.keys(patient.maritalStatus).every(codingFields)
311
- && typeof patient.maritalStatus.text === 'string'
312
- && patient.maritalStatus.text.match('Married|Divorced|Widowed|unmarried|unknown') != null;
313
- }
314
- if (patient.photo) {
315
- patient.photo.forEach(aPhoto => respuesta = respuesta && Object.keys(aPhoto).every(photoFields));
316
- }
317
- if (patient.contact) {
318
- patient.contact.forEach(aContact => {
319
- if (aContact.relationship) {
320
- aContact.relationship.forEach(aRelation => respuesta = respuesta && Object.keys(aRelation).every(codingFields));
321
- }
322
- if (aContact.name) {
323
- respuesta = respuesta && validName(aContact.name);
324
- }
325
- });
326
- }
327
- } else {
328
- respuesta = fieldVerified; // No cumple con los campos estandar de FHIR
329
- }
330
- return respuesta;
331
- }
332
-
333
- function pacienteFHIRFields(elem) {
334
- return elem.match('resourceType|identifier|active|name|telecom|gender|birthDate|deceasedBoolean|deceasedDateTime|address|maritalStatus|photo|contact') != null;
335
- }
336
-
337
- function identifierFields(elem) {
338
- return elem.match('use|type|system|value|period|assigner') != null;
339
- }
340
-
341
- function nameFields(elem) {
342
- return elem.match('resourceType|family|given') != null;
343
- }
344
-
345
- function telecomFields(elem) {
346
- return elem.match('resourceType|system|value|use|rank|period') != null;
347
- }
348
-
349
- function addressFields(elem) {
350
- return elem.match('resourceType|use|type|text|line|city|district|state|postalCode|country|period') != null;
351
- }
352
-
353
- function codingFields(elem) {
354
- return elem.match('coding|text') != null;
355
- }
356
-
357
- function photoFields(elem) {
358
- return elem.match('contentType|language|data|url|size|hash|title|creation') != null;
359
- }
360
-
361
- function contactFields(elem) {
362
- return elem.match('relationship|name|telecom|address|gender|organization|period') != null;
363
- }
364
-
365
- function validName(aName) {
366
- return Object.keys(aName).every(nameFields) && ('resourceType' in aName) && aName.resourceType === 'HumanName' &&
367
- ('family' in aName) && Array.isArray(aName.family) && aName.family.every(areStrings) &&
368
- ('given' in aName) && Array.isArray(aName.given) && aName.given.every(areStrings);
369
- }
370
-
371
- function areStrings(elem) {
372
- return typeof elem === 'string';
373
- }
374
-
1
+
2
+ import { getDominio, makeUrl } from './config';
3
+
4
+ /**
5
+ * Encode a patient from ANDES to FHIR
6
+ * @param {} patient
7
+ */
8
+ export function encode(patient) {
9
+ if (patient) {
10
+ const identificadores: any = [];
11
+ if (patient.documento) {
12
+ identificadores.push({
13
+ system: 'http://www.renaper.gob.ar/dni',
14
+ value: patient.documento
15
+ });
16
+ }
17
+ if (patient.cuil) {
18
+ identificadores.push({
19
+ system: 'http://www.renaper.gob.ar/cuil',
20
+ value: patient.cuil
21
+ });
22
+ }
23
+ identificadores.push({
24
+ system: getDominio(),
25
+ value: patient._id
26
+ });
27
+ // Parsea contactos
28
+ let contactos = patient.contacto ? patient.contacto.filter(c => c.valor).map(unContacto => {
29
+ let cont = {
30
+ resourceType: 'ContactPoint',
31
+ value: unContacto.valor,
32
+ rank: unContacto.ranking,
33
+ };
34
+ switch (unContacto.tipo) {
35
+ case 'fijo':
36
+ cont['system'] = 'phone';
37
+ break;
38
+ case 'celular':
39
+ cont['system'] = 'phone';
40
+ break;
41
+ case 'email':
42
+ cont['system'] = 'email';
43
+ break;
44
+ }
45
+ return cont;
46
+ }) : [];
47
+ // Parsea direcciones
48
+ let direcciones = patient.direccion ? patient.direccion.filter(dir => dir.ubicacion.localidad).map(unaDireccion => {
49
+ let direc = {
50
+ resourceType: 'Address',
51
+ postalCode: unaDireccion.codigoPostal ? unaDireccion.codigoPostal : '',
52
+ line: [unaDireccion.valor],
53
+ city: unaDireccion.ubicacion.localidad ? unaDireccion.ubicacion.localidad.nombre : '',
54
+ state: unaDireccion.ubicacion.provincia ? unaDireccion.ubicacion.provincia.nombre : '',
55
+ country: unaDireccion.ubicacion.pais ? unaDireccion.ubicacion.pais.nombre : '',
56
+ };
57
+ return direc;
58
+ }) : [];
59
+ // Parsea relaciones
60
+ let relaciones = patient.relaciones ? patient.relaciones.filter(r => !!r.relacion).map(unaRelacion => {
61
+ let relacion = {
62
+ relationship: [{
63
+ text: unaRelacion.relacion.nombre
64
+ }], // The kind of relationship
65
+ name: {
66
+ resourceType: 'HumanName',
67
+ family: unaRelacion.apellido.split(' '), // Family name (often called 'Surname')
68
+ given: unaRelacion.nombre.split(' '), // Given names (not always 'first'). Includes middle names
69
+ }
70
+ };
71
+ return relacion;
72
+ }) : [];
73
+ let genero;
74
+ switch (patient.genero) {
75
+ case 'femenino':
76
+ genero = 'female';
77
+ break;
78
+ case 'masculino':
79
+ genero = 'male';
80
+ break;
81
+ case 'otro':
82
+ genero = 'other';
83
+ break;
84
+ }
85
+ let pacienteFHIR: any = {
86
+ id: patient.id,
87
+ resourceType: 'Patient',
88
+ identifier: identificadores,
89
+ active: patient.activo ? patient.activo : null, // Whether this patient's record is in active use
90
+ name: [{
91
+ use: 'official',
92
+ resourceType: 'HumanName',
93
+ family: patient.apellido.split(' '),
94
+ given: patient.nombre.split(' '),
95
+ text: `${patient.nombre} ${patient.apellido}`,
96
+ _family: [{
97
+ extension: [
98
+ {
99
+ url: 'http://hl7.org/fhir/StructureDefinition/humanname-fathers-family',
100
+ valueString: patient.apellido
101
+ },
102
+ ]
103
+ }],
104
+ }],
105
+ gender: genero, // male | female | other | unknown
106
+ birthDate: patient.fechaNacimiento ? typeof patient.fechaNacimiento === 'string' ? new Date(patient.fechaNacimiento).toISOString().slice(0, 10) : patient.fechaNacimiento.toISOString().slice(0, 10) : null,
107
+ extension: [{
108
+ url: 'andes.gob.ar/fhir/StructureDefinition/patient-status',
109
+ valueCode: patient.estado ? patient.estado : 'temporal' // validado | temporal
110
+ }]
111
+ };
112
+ if (patient.fechaFallecimiento) {
113
+ pacienteFHIR.deceasedDateTime = typeof patient.fechaFallecimiento === 'string' ? new Date(patient.fechaFallecimiento.toISOString().slice(0, 10)) : patient.fechaFallecimiento.toISOString().slice(0, 10);
114
+ }
115
+ if (patient.estadoCivil) {
116
+ let estadoCivil;
117
+ switch (patient.estadoCivil) {
118
+ case 'casado':
119
+ estadoCivil = 'Married';
120
+ break;
121
+ case 'divorciado':
122
+ estadoCivil = 'Divorced';
123
+ break;
124
+ case 'viudo':
125
+ estadoCivil = 'Widowed';
126
+ break;
127
+ case 'soltero':
128
+ estadoCivil = 'unmarried';
129
+ break;
130
+ default:
131
+ estadoCivil = 'unknown';
132
+ break;
133
+ }
134
+ pacienteFHIR['maritalStatus'] = {
135
+ text: estadoCivil
136
+ };
137
+ }
138
+ if (patient.foto) {
139
+ pacienteFHIR['photo'] = [{
140
+ patient: patient.foto
141
+ }];
142
+ }
143
+ if (contactos.length > 0) { // A contact detail for the individual
144
+ pacienteFHIR['telecom'] = contactos;
145
+ }
146
+ if (direcciones.length > 0) { // Addresses for the individual
147
+ pacienteFHIR['address'] = direcciones;
148
+ }
149
+ if (relaciones.length > 0) { // A contact party (e.g. guardian, partner, friend) for the patient
150
+ pacienteFHIR['contact'] = relaciones;
151
+ }
152
+ let managingOrganization = {
153
+ reference: patient.createdBy.organizacion.id,
154
+ display: patient.createdBy.organizacion.nombre
155
+ };
156
+ pacienteFHIR['managingOrganization'] = managingOrganization;
157
+ return pacienteFHIR;
158
+ } else {
159
+ return null;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Decode a patient from FHIR to ANDES
165
+ * @param {} patient
166
+ */
167
+ export function decode(patient) {
168
+ let genero: string;
169
+ let sexo: string;
170
+ // Cuando el paciente viene por FHIR suponemos el valor del genero tanto para nuestro field genero como sexo
171
+ switch (patient.gender) {
172
+ case 'female':
173
+ genero = 'femenino';
174
+ sexo = 'femenino';
175
+ break;
176
+ case 'male':
177
+ genero = 'masculino';
178
+ sexo = 'masculino';
179
+ break;
180
+ case 'other':
181
+ genero = 'otro';
182
+ sexo = 'otro';
183
+ break;
184
+ }
185
+ function getValue(items, key) {
186
+ const element = items.find(el => el.system === key);
187
+ if (element) {
188
+ return element.value;
189
+ }
190
+ }
191
+ let pacienteAndes = {
192
+ id: getValue(patient.identifier, makeUrl('Patient')),
193
+ documento: getValue(patient.identifier, 'http://www.renaper.gob.ar/dni'),
194
+ nombre: patient.name[0].given.join().replace(',', ' '),
195
+ apellido: Array.isArray(patient.name[0].family) ? patient.name[0].family.join().replace(',', ' ') : patient.name[0].family,
196
+ fechaNacimiento: patient.birthDate,
197
+ genero,
198
+ sexo,
199
+ estado: 'temporal', // Todos los pacientes que recibimos por Fhir son considerados temporales en su conversión.
200
+ createdBy: null
201
+ };
202
+ let contactos = patient.telecom ? patient.telecom.map(unContacto => {
203
+ let cont = {
204
+ valor: unContacto.value,
205
+ ranking: unContacto.rank
206
+ };
207
+ switch (unContacto.system) {
208
+ case 'phone':
209
+ cont['tipo'] = 'celular';
210
+ break;
211
+ case 'email':
212
+ cont['tipo'] = 'email';
213
+ break;
214
+ }
215
+ return cont;
216
+ }) : [];
217
+
218
+ let relaciones = patient.contact ? patient.contact.map(aContact => {
219
+ let relacion = {
220
+ relacion: {
221
+ nombre: aContact.relationship[0].text
222
+ },
223
+ nombre: aContact.name.given.join().replace(',', ' '),
224
+ apellido: aContact.name.family
225
+ };
226
+ return relacion;
227
+ }) : [];
228
+
229
+ if (patient.maritalStatus) {
230
+ let estadoCivil;
231
+ switch (patient.maritalStatus['text']) {
232
+ case 'Married':
233
+ estadoCivil = 'casado';
234
+ break;
235
+ case 'Divorced':
236
+ estadoCivil = 'divorciado';
237
+ break;
238
+ case 'Widowed':
239
+ estadoCivil = 'viudo';
240
+ break;
241
+ case 'unmarried':
242
+ estadoCivil = 'soltero';
243
+ break;
244
+ default:
245
+ estadoCivil = 'otro';
246
+ break;
247
+ }
248
+ pacienteAndes['estadoCivil'] = estadoCivil;
249
+ }
250
+
251
+ let direcciones = patient.address ? patient.address.map(unaDireccion => {
252
+ let dir = {
253
+ activo: true,
254
+ valor: unaDireccion.line,
255
+ codigoPostal: unaDireccion.postalCode,
256
+ // TODO: En un MATETIME ver si la información de pais, provincia, localidad
257
+ // se delega en el insert o update. Y este decoder sólo guarda la info tal cual viene.
258
+ ubicacion: {
259
+ pais: unaDireccion.country,
260
+ provincia: unaDireccion.state,
261
+ localidad: unaDireccion.city
262
+ }
263
+ };
264
+ return dir;
265
+ }) : [];
266
+
267
+ pacienteAndes['direccion'] = direcciones;
268
+
269
+ if (patient.active) {
270
+ pacienteAndes['activo'] = patient.active;
271
+ }
272
+ if (contactos.length > 0) {
273
+ pacienteAndes['contacto'] = contactos;
274
+ }
275
+ if (patient.photo) { // Image of the patient
276
+ pacienteAndes['foto'] = patient.photo[0].data;
277
+ }
278
+
279
+ if (patient.contact) {
280
+ pacienteAndes['relaciones'] = relaciones;
281
+ }
282
+
283
+ if (patient.managingOrganization) {
284
+ pacienteAndes['createdBy'] = {
285
+ organizacion: {
286
+ id: patient.managingOrganization.reference,
287
+ nombre: patient.managingOrganization.display
288
+ }
289
+ };
290
+ }
291
+
292
+ return pacienteAndes;
293
+ }
294
+
295
+ /**
296
+ * Verify if a patient has a FHIR format
297
+ * @param {*} patient
298
+ */
299
+ export function verify(patient) {
300
+ let respuesta = true;
301
+ let fieldVerified = Object.keys(patient).every(pacienteFHIRFields);
302
+ // Verificamos que las key esten contenidas en los conjuntos mínimos pacienteFHIRField
303
+ if (fieldVerified) {
304
+ respuesta = ('resourceType' in patient) && patient.resourceType === 'Patient';
305
+ respuesta = respuesta && ('identifier' in patient);
306
+ if (patient.identifier.length > 0) {
307
+ patient.identifier.forEach(anIdentifier => respuesta = respuesta && Object.keys(anIdentifier).every(identifierFields));
308
+
309
+ }
310
+ patient.name.forEach(aName => respuesta = respuesta && validName(aName));
311
+ if (patient.active) {
312
+ respuesta = respuesta && (typeof patient.active === 'boolean');
313
+ }
314
+ if (patient.telecom) {
315
+ patient.telecom.forEach(aTelecom => respuesta = respuesta && Object.keys(aTelecom).every(telecomFields));
316
+ }
317
+ if (patient.gender) {
318
+ respuesta = respuesta && patient.gender.match('male|female|other|unknown') != null;
319
+ }
320
+ if (patient.birthDate) {
321
+ respuesta = respuesta && typeof patient.birthDate === 'string'; // TODO: Algun control de que tenga formato Date
322
+ }
323
+ if (patient.deceasedDateTime) {
324
+ respuesta = respuesta && typeof patient.deceasedDateTime === 'string'; // TODO: Algun control de que tenga formato DateTime
325
+ }
326
+ if (patient.address) {
327
+ patient.address.forEach(anAddress => respuesta = respuesta && Object.keys(anAddress).every(addressFields));
328
+ }
329
+ if (patient.maritalStatus && patient.maritalStatus.text) {
330
+ respuesta = respuesta && Object.keys(patient.maritalStatus).every(codingFields)
331
+ && typeof patient.maritalStatus.text === 'string'
332
+ && patient.maritalStatus.text.match('Married|Divorced|Widowed|unmarried|unknown') != null;
333
+ }
334
+ if (patient.photo) {
335
+ patient.photo.forEach(aPhoto => respuesta = respuesta && Object.keys(aPhoto).every(photoFields));
336
+ }
337
+ if (patient.contact) {
338
+ patient.contact.forEach(aContact => {
339
+ if (aContact.relationship) {
340
+ aContact.relationship.forEach(aRelation => respuesta = respuesta && Object.keys(aRelation).every(codingFields));
341
+ }
342
+ if (aContact.name) {
343
+ respuesta = respuesta && validName(aContact.name);
344
+ }
345
+ });
346
+ }
347
+ } else {
348
+ respuesta = fieldVerified; // No cumple con los campos estandar de FHIR
349
+ }
350
+ return respuesta;
351
+ }
352
+
353
+ function pacienteFHIRFields(elem) {
354
+ return elem.match('resourceType|identifier|active|name|telecom|gender|birthDate|deceasedBoolean|deceasedDateTime|address|maritalStatus|photo|contact') != null;
355
+ }
356
+
357
+ function identifierFields(elem) {
358
+ return elem.match('use|type|system|value|period|assigner') != null;
359
+ }
360
+
361
+ function nameFields(elem) {
362
+ return elem.match('resourceType|family|given') != null;
363
+ }
364
+
365
+ function telecomFields(elem) {
366
+ return elem.match('resourceType|system|value|use|rank|period') != null;
367
+ }
368
+
369
+ function addressFields(elem) {
370
+ return elem.match('resourceType|use|type|text|line|city|district|state|postalCode|country|period') != null;
371
+ }
372
+
373
+ function codingFields(elem) {
374
+ return elem.match('coding|text') != null;
375
+ }
376
+
377
+ function photoFields(elem) {
378
+ return elem.match('contentType|language|data|url|size|hash|title|creation') != null;
379
+ }
380
+
381
+ function contactFields(elem) {
382
+ return elem.match('relationship|name|telecom|address|gender|organization|period') != null;
383
+ }
384
+
385
+ function validName(aName) {
386
+ return Object.keys(aName).every(nameFields) && ('resourceType' in aName) && aName.resourceType === 'HumanName' &&
387
+ ('family' in aName) && Array.isArray(aName.family) && aName.family.every(areStrings) &&
388
+ ('given' in aName) && Array.isArray(aName.given) && aName.given.every(areStrings);
389
+ }
390
+
391
+ function areStrings(elem) {
392
+ return typeof elem === 'string';
393
+ }
394
+