@openmrs/esm-utils 5.6.1-pre.1801 → 5.6.1-pre.1806
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.
- package/.turbo/turbo-build.log +5 -5
- package/dist/openmrs-esm-utils.js +1 -1
- package/dist/openmrs-esm-utils.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/patient-helpers.test.data.ts +146 -0
- package/src/patient-helpers.test.ts +69 -0
- package/src/patient-helpers.ts +85 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-utils",
|
|
3
|
-
"version": "5.6.1-pre.
|
|
3
|
+
"version": "5.6.1-pre.1806",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Helper utilities for OpenMRS",
|
|
6
6
|
"browser": "dist/openmrs-esm-utils.js",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@openmrs/esm-globals": "5.6.1-pre.
|
|
42
|
+
"@openmrs/esm-globals": "5.6.1-pre.1806",
|
|
43
43
|
"@types/semver": "^7.3.4",
|
|
44
44
|
"dayjs": "^1.10.4",
|
|
45
45
|
"rxjs": "^6.5.3"
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export const nameWithFormat = {
|
|
2
|
+
id: 'efdb246f-4142-4c12-a27a-9be60b9592e9',
|
|
3
|
+
family: 'Wilson',
|
|
4
|
+
given: ['John'],
|
|
5
|
+
text: 'Wilson, John',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const nameWithoutFormat = {
|
|
9
|
+
id: 'efdb246f-4142-4c12-a27a-9be60b9592e9',
|
|
10
|
+
family: 'family name',
|
|
11
|
+
given: ['given', 'middle'],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const familyNameOnly = {
|
|
15
|
+
id: 'efdb246f-4142-4c12-a27a-9be60b9592e9',
|
|
16
|
+
family: 'family name',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const givenNameOnly = {
|
|
20
|
+
id: 'efdb246f-4142-4c12-a27a-9be60b9592e9',
|
|
21
|
+
given: ['given'],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const mockPatient = {
|
|
25
|
+
resourceType: 'Patient',
|
|
26
|
+
id: '8673ee4f-e2ab-4077-ba55-4980f408773e',
|
|
27
|
+
extension: [
|
|
28
|
+
{
|
|
29
|
+
url: 'http://fhir-es.transcendinsights.com/stu3/StructureDefinition/resource-date-created',
|
|
30
|
+
valueDateTime: '2017-01-18T09:42:40+00:00',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
url: 'https://purl.org/elab/fhir/StructureDefinition/Creator-crew-version1',
|
|
34
|
+
valueString: 'daemon',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
identifier: [
|
|
38
|
+
{
|
|
39
|
+
id: '1f0ad7a1-430f-4397-b571-59ea654a52db',
|
|
40
|
+
use: 'secondary',
|
|
41
|
+
system: 'Old Identification Number',
|
|
42
|
+
value: '100732HE',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: '1f0ad7a1-430f-4397-b571-59ea654a52db',
|
|
46
|
+
use: 'usual',
|
|
47
|
+
system: 'OpenMRS ID',
|
|
48
|
+
value: '100GEJ',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
active: true,
|
|
52
|
+
name: [nameWithFormat],
|
|
53
|
+
gender: 'male',
|
|
54
|
+
birthDate: '1972-04-04',
|
|
55
|
+
deceasedBoolean: false,
|
|
56
|
+
address: [],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const mockPatientWithNoName = {
|
|
60
|
+
...mockPatient,
|
|
61
|
+
name: [],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const mockPatientWithMultipleNames = {
|
|
65
|
+
// name usage may be: usual | official | temp | nickname | anonymous | old | maiden
|
|
66
|
+
...mockPatient,
|
|
67
|
+
name: [
|
|
68
|
+
{
|
|
69
|
+
id: 'id-of-nickname-1',
|
|
70
|
+
use: 'nickname',
|
|
71
|
+
given: ['nick', 'name'],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'id-of-nickname-2',
|
|
75
|
+
use: 'nickname',
|
|
76
|
+
given: ['nick', 'name'],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'id-of-anonymous-name-1',
|
|
80
|
+
use: 'anonymous',
|
|
81
|
+
given: ['john', 'doe'],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'id-of-old-name-1',
|
|
85
|
+
use: 'old',
|
|
86
|
+
given: ['previous'],
|
|
87
|
+
family: 'name',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'id-of-maiden-name-1',
|
|
91
|
+
use: 'maiden',
|
|
92
|
+
family: 'maiden name',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
// this is the actual display name
|
|
96
|
+
id: 'id-of-usual-name-1',
|
|
97
|
+
given: ['John', 'Murray'],
|
|
98
|
+
family: 'Smith',
|
|
99
|
+
text: 'Smith, John Murray',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
// this is usable as a display name, but the usual name will take precedence.
|
|
103
|
+
id: 'id-of-official-name-1',
|
|
104
|
+
use: 'official',
|
|
105
|
+
given: ['my', 'official'],
|
|
106
|
+
family: 'name',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const mockPatientWithOfficialName = {
|
|
112
|
+
...mockPatient,
|
|
113
|
+
name: [
|
|
114
|
+
{
|
|
115
|
+
// this is usable as a display name, but the usual name should be preferred
|
|
116
|
+
id: 'id-of-official-name-1',
|
|
117
|
+
use: 'official',
|
|
118
|
+
given: ['my', 'official'],
|
|
119
|
+
family: 'name',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
// this is the preferred display name, even though it comes after the official name
|
|
123
|
+
id: 'id-of-usual-name-1',
|
|
124
|
+
use: 'usual', // explicitly marked as usual name
|
|
125
|
+
given: ['my', 'actual'],
|
|
126
|
+
family: 'name',
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const mockPatientWithNickAndOfficialName = {
|
|
132
|
+
...mockPatient,
|
|
133
|
+
name: [
|
|
134
|
+
{
|
|
135
|
+
id: 'id-of-nickname-1',
|
|
136
|
+
use: 'nickname',
|
|
137
|
+
given: ['nick', 'name'],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 'id-of-official-name-1',
|
|
141
|
+
use: 'official',
|
|
142
|
+
given: ['my', 'official'],
|
|
143
|
+
family: 'name',
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { displayName, formattedName, selectPreferredName } from './patient-helpers';
|
|
2
|
+
import {
|
|
3
|
+
mockPatientWithNoName,
|
|
4
|
+
mockPatientWithOfficialName,
|
|
5
|
+
nameWithFormat,
|
|
6
|
+
nameWithoutFormat,
|
|
7
|
+
familyNameOnly,
|
|
8
|
+
givenNameOnly,
|
|
9
|
+
mockPatientWithMultipleNames,
|
|
10
|
+
mockPatientWithNickAndOfficialName,
|
|
11
|
+
} from './patient-helpers.test.data';
|
|
12
|
+
import { type NameUse } from '../../esm-api';
|
|
13
|
+
|
|
14
|
+
describe('Formatted display name', () => {
|
|
15
|
+
it.each([
|
|
16
|
+
[nameWithFormat, 'Wilson, John'],
|
|
17
|
+
[nameWithoutFormat, 'given middle family name'],
|
|
18
|
+
[familyNameOnly, 'family name'],
|
|
19
|
+
[givenNameOnly, 'given'],
|
|
20
|
+
[mockPatientWithNoName, ''],
|
|
21
|
+
])('Is formatted name text if present else default name format', (name, expected) => {
|
|
22
|
+
const result = formattedName(name);
|
|
23
|
+
expect(result).toBe(expected);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('Patient display name', () => {
|
|
28
|
+
it.each([
|
|
29
|
+
[mockPatientWithMultipleNames, 'Smith, John Murray'],
|
|
30
|
+
[mockPatientWithOfficialName, 'my actual name'],
|
|
31
|
+
[mockPatientWithNickAndOfficialName, 'my official name'],
|
|
32
|
+
])('Is selected from usual name or official name', (patient, expected) => {
|
|
33
|
+
const result = displayName(patient);
|
|
34
|
+
expect(result).toBe(expected);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const usual: NameUse = 'usual';
|
|
39
|
+
const official: NameUse = 'official';
|
|
40
|
+
const maiden: NameUse = 'maiden';
|
|
41
|
+
const nickname: NameUse = 'nickname';
|
|
42
|
+
const temp: NameUse = 'temp';
|
|
43
|
+
|
|
44
|
+
describe('Preferred patient name', () => {
|
|
45
|
+
it.each([
|
|
46
|
+
[mockPatientWithMultipleNames, [], 'id-of-usual-name-1'],
|
|
47
|
+
[mockPatientWithMultipleNames, [usual], 'id-of-usual-name-1'],
|
|
48
|
+
[mockPatientWithMultipleNames, [usual, official], 'id-of-usual-name-1'],
|
|
49
|
+
[mockPatientWithMultipleNames, [official], 'id-of-official-name-1'],
|
|
50
|
+
[mockPatientWithMultipleNames, [official, usual], 'id-of-official-name-1'],
|
|
51
|
+
[mockPatientWithMultipleNames, [maiden, usual, official], 'id-of-maiden-name-1'],
|
|
52
|
+
[mockPatientWithOfficialName, [usual, official], 'id-of-usual-name-1'],
|
|
53
|
+
[mockPatientWithNickAndOfficialName, [nickname, official], 'id-of-nickname-1'],
|
|
54
|
+
])('Is selected according to preferred usage', (patient, preferredUsage, expectedNameId) => {
|
|
55
|
+
const result = selectPreferredName(patient, ...preferredUsage);
|
|
56
|
+
expect(result?.id).toBe(expectedNameId);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('Preferred patient name', () => {
|
|
61
|
+
it.each([
|
|
62
|
+
[mockPatientWithMultipleNames, [temp]],
|
|
63
|
+
[mockPatientWithNoName, [usual]],
|
|
64
|
+
[mockPatientWithNoName, []],
|
|
65
|
+
])('Is empty if preferred name is not present.', (patient, preferredUsage) => {
|
|
66
|
+
const result = selectPreferredName(patient, ...preferredUsage);
|
|
67
|
+
expect(result).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** @module @category Utility */
|
|
2
|
+
|
|
3
|
+
import type { NameUse } from '../../esm-api';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Gets the formatted display name for a patient.
|
|
7
|
+
*
|
|
8
|
+
* The display name will be taken from the patient's 'usual' name,
|
|
9
|
+
* or may fall back to the patient's 'official' name.
|
|
10
|
+
*
|
|
11
|
+
* @param patient The patient details in FHIR format.
|
|
12
|
+
* @returns The patient's display name or an empty string if name is not present.
|
|
13
|
+
*/
|
|
14
|
+
export function displayName(patient: fhir.Patient): string {
|
|
15
|
+
const name = selectPreferredName(patient, 'usual', 'official');
|
|
16
|
+
return formattedName(name);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get a formatted display string for an FHIR name.
|
|
21
|
+
* @param name The name to be formatted.
|
|
22
|
+
* @returns The formatted display name or an empty string if name is undefined.
|
|
23
|
+
*/
|
|
24
|
+
export function formattedName(name: fhir.HumanName | undefined): string {
|
|
25
|
+
if (name) return name.text ?? defaultFormat(name);
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Select the preferred name from the collection of names associated with a patient.
|
|
31
|
+
*
|
|
32
|
+
* Names may be specified with a usage such as 'usual', 'official', 'nickname', 'maiden', etc.
|
|
33
|
+
* A name with no usage specified is treated as the 'usual' name.
|
|
34
|
+
*
|
|
35
|
+
* The chosen name will be selected according to the priority order of `preferredNames`,
|
|
36
|
+
* @example
|
|
37
|
+
* // normal use case; prefer usual name, fallback to official name
|
|
38
|
+
* displayNameByUsage(patient, 'usual', 'official')
|
|
39
|
+
* @example
|
|
40
|
+
* // prefer usual name over nickname, fallback to official name
|
|
41
|
+
* displayNameByUsage(patient, 'usual', 'nickname', 'official')
|
|
42
|
+
*
|
|
43
|
+
* @param patient The patient from whom a name will be selected.
|
|
44
|
+
* @param preferredNames Optional ordered sequence of preferred name usages; defaults to 'usual' if not specified.
|
|
45
|
+
* @return the preferred name for the patient, or undefined if no acceptable name could be found.
|
|
46
|
+
*/
|
|
47
|
+
export function selectPreferredName(patient: fhir.Patient, ...preferredNames: NameUse[]): fhir.HumanName | undefined {
|
|
48
|
+
if (preferredNames.length == 0) {
|
|
49
|
+
preferredNames = ['usual'];
|
|
50
|
+
}
|
|
51
|
+
for (const usage of preferredNames) {
|
|
52
|
+
const name = patient.name?.find((name) => nameUsageMatches(name, usage));
|
|
53
|
+
if (name) {
|
|
54
|
+
return name;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generate a display name by concatenating forenames and surname.
|
|
62
|
+
* @param name the person's name.
|
|
63
|
+
* @returns the person's name as a string.
|
|
64
|
+
*/
|
|
65
|
+
function defaultFormat(name: fhir.HumanName): string {
|
|
66
|
+
const forenames: string[] = name.given ?? [];
|
|
67
|
+
const names: string[] = name.family ? forenames.concat(name.family) : forenames;
|
|
68
|
+
return names.join(' ');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Determine whether the usage of a given name matches the given NameUse.
|
|
73
|
+
*
|
|
74
|
+
* A name with no usage is treated as the 'usual' name.
|
|
75
|
+
*
|
|
76
|
+
* @param name the name to test.
|
|
77
|
+
* @param usage the NameUse to test for.
|
|
78
|
+
*/
|
|
79
|
+
function nameUsageMatches(name: fhir.HumanName, usage: NameUse): boolean {
|
|
80
|
+
if (!name.use)
|
|
81
|
+
// a name with no usage is treated as 'usual'
|
|
82
|
+
return usage === 'usual';
|
|
83
|
+
|
|
84
|
+
return name.use === usage;
|
|
85
|
+
}
|