gdc-common-utils-ts 2.1.0 → 2.2.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.
- package/README.md +18 -0
- package/dist/claims/claims-helpers-allergy-intolerance.d.ts +8 -0
- package/dist/claims/claims-helpers-allergy-intolerance.js +30 -12
- package/dist/claims/claims-helpers-condition.d.ts +8 -0
- package/dist/claims/claims-helpers-condition.js +30 -12
- package/dist/claims/claims-helpers-consent.d.ts +4 -0
- package/dist/claims/claims-helpers-consent.js +15 -1
- package/dist/claims/claims-helpers-diagnostic-report.d.ts +4 -0
- package/dist/claims/claims-helpers-diagnostic-report.js +17 -2
- package/dist/claims/claims-helpers-medication-statement.d.ts +10 -2
- package/dist/claims/claims-helpers-medication-statement.js +32 -14
- package/dist/constants/didcomm.d.ts +22 -0
- package/dist/constants/didcomm.js +22 -0
- package/dist/constants/fhir-resource-types.d.ts +2 -0
- package/dist/constants/fhir-resource-types.js +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/constants/lifecycle.d.ts +2 -0
- package/dist/constants/lifecycle.js +2 -0
- package/dist/constants/verifiable-credentials.d.ts +9 -0
- package/dist/constants/verifiable-credentials.js +9 -0
- package/dist/examples/bundle-didcomm-payload.js +4 -2
- package/dist/examples/communication-attached-bundle-session.js +27 -27
- package/dist/examples/communication-didcomm-payload.d.ts +8 -4
- package/dist/examples/communication-didcomm-payload.js +17 -11
- package/dist/examples/consent-access.js +2 -1
- package/dist/examples/employee.d.ts +21 -0
- package/dist/examples/employee.js +21 -0
- package/dist/examples/individual-controller.d.ts +27 -0
- package/dist/examples/individual-controller.js +28 -1
- package/dist/examples/inter-tenant-access-contract.js +3 -2
- package/dist/examples/ips-bundle.js +17 -16
- package/dist/examples/lifecycle.js +13 -11
- package/dist/examples/profile-manager-mem.js +2 -1
- package/dist/examples/related-person.d.ts +14 -0
- package/dist/examples/related-person.js +18 -4
- package/dist/examples/shared.d.ts +12 -3
- package/dist/examples/shared.js +13 -4
- package/dist/examples/vital-signs.js +1 -1
- package/dist/examples/wallet-mem.js +4 -2
- package/dist/models/authority-resolution.d.ts +56 -0
- package/dist/models/authority-resolution.js +2 -0
- package/dist/models/bundle-editor-types.d.ts +89 -0
- package/dist/models/bundle-editor-types.js +45 -0
- package/dist/models/bundle.d.ts +5 -0
- package/dist/models/communication-attached-bundle-session.d.ts +6 -4
- package/dist/models/communication-attached-bundle-session.js +6 -4
- package/dist/models/confidential-storage.d.ts +2 -1
- package/dist/models/consent-rule.d.ts +11 -1
- package/dist/models/consent-rule.js +9 -1
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.js +2 -0
- package/dist/models/individual-onboarding.d.ts +18 -0
- package/dist/models/interoperable-claims/allergy-intolerance-claims.d.ts +12 -1
- package/dist/models/interoperable-claims/allergy-intolerance-claims.js +12 -1
- package/dist/models/interoperable-claims/condition-claims.d.ts +12 -1
- package/dist/models/interoperable-claims/condition-claims.js +12 -1
- package/dist/models/interoperable-claims/diagnostic-report-claims.d.ts +13 -2
- package/dist/models/interoperable-claims/diagnostic-report-claims.js +12 -1
- package/dist/models/interoperable-claims/document-reference-claims.d.ts +1 -0
- package/dist/models/interoperable-claims/document-reference-claims.js +3 -0
- package/dist/models/interoperable-claims/medication-statement-claims.d.ts +17 -0
- package/dist/models/interoperable-claims/medication-statement-claims.js +17 -0
- package/dist/utils/allergy-intolerance-entry-editor.d.ts +51 -0
- package/dist/utils/allergy-intolerance-entry-editor.js +55 -0
- package/dist/utils/authority-resolution.d.ts +37 -0
- package/dist/utils/authority-resolution.js +112 -0
- package/dist/utils/backend-message-manager-mem.d.ts +11 -0
- package/dist/utils/backend-message-manager-mem.js +11 -0
- package/dist/utils/bundle-didcomm-payload.js +3 -2
- package/dist/utils/bundle-document-builder.d.ts +1 -0
- package/dist/utils/bundle-document-builder.js +134 -14
- package/dist/utils/bundle-editor-core.d.ts +181 -0
- package/dist/utils/bundle-editor-core.js +467 -0
- package/dist/utils/bundle-editor-helpers.d.ts +53 -0
- package/dist/utils/bundle-editor-helpers.js +146 -0
- package/dist/utils/bundle-editor-registry.d.ts +6 -0
- package/dist/utils/bundle-editor-registry.js +14 -0
- package/dist/utils/bundle-editor.d.ts +21 -722
- package/dist/utils/bundle-editor.js +21 -1601
- package/dist/utils/bundle-entry-editor.d.ts +96 -0
- package/dist/utils/bundle-entry-editor.js +305 -0
- package/dist/utils/bundle-query.d.ts +1 -0
- package/dist/utils/bundle-query.js +12 -3
- package/dist/utils/bundle-reader.d.ts +48 -0
- package/dist/utils/bundle-reader.js +175 -1
- package/dist/utils/care-plan-entry-editor.d.ts +43 -0
- package/dist/utils/care-plan-entry-editor.js +47 -0
- package/dist/utils/client-assertion.d.ts +38 -0
- package/dist/utils/client-assertion.js +113 -0
- package/dist/utils/clinical-impression-entry-editor.d.ts +43 -0
- package/dist/utils/clinical-impression-entry-editor.js +47 -0
- package/dist/utils/clinical-resource-entry-editor.d.ts +123 -0
- package/dist/utils/clinical-resource-entry-editor.js +296 -0
- package/dist/utils/communication-attached-bundle-session-helpers.js +8 -6
- package/dist/utils/communication-attached-bundle-session.d.ts +29 -3
- package/dist/utils/communication-attached-bundle-session.js +62 -3
- package/dist/utils/communication-didcomm-payload.d.ts +5 -0
- package/dist/utils/communication-didcomm-payload.js +28 -18
- package/dist/utils/communication-document-reference.d.ts +23 -0
- package/dist/utils/communication-document-reference.js +89 -0
- package/dist/utils/communication-editor.d.ts +53 -0
- package/dist/utils/communication-editor.js +97 -0
- package/dist/utils/condition-entry-editor.d.ts +30 -0
- package/dist/utils/condition-entry-editor.js +34 -0
- package/dist/utils/consent-claim-helpers.d.ts +4 -0
- package/dist/utils/consent-claim-helpers.js +15 -1
- package/dist/utils/coverage-entry-editor.d.ts +51 -0
- package/dist/utils/coverage-entry-editor.js +55 -0
- package/dist/utils/device-entry-editor.d.ts +59 -0
- package/dist/utils/device-entry-editor.js +63 -0
- package/dist/utils/device-use-statement-entry-editor.d.ts +43 -0
- package/dist/utils/device-use-statement-entry-editor.js +47 -0
- package/dist/utils/diagnostic-report-entry-editor.d.ts +74 -0
- package/dist/utils/diagnostic-report-entry-editor.js +136 -0
- package/dist/utils/did.d.ts +8 -0
- package/dist/utils/did.js +22 -0
- package/dist/utils/document-reference-entry-editor.d.ts +62 -0
- package/dist/utils/document-reference-entry-editor.js +66 -0
- package/dist/utils/employee-entry-editor.d.ts +36 -0
- package/dist/utils/employee-entry-editor.js +88 -0
- package/dist/utils/employee.d.ts +1 -0
- package/dist/utils/employee.js +2 -1
- package/dist/utils/encounter-entry-editor.d.ts +51 -0
- package/dist/utils/encounter-entry-editor.js +55 -0
- package/dist/utils/flag-entry-editor.d.ts +47 -0
- package/dist/utils/flag-entry-editor.js +51 -0
- package/dist/utils/immunization-entry-editor.d.ts +87 -0
- package/dist/utils/immunization-entry-editor.js +169 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/individual-onboarding-acceptance-credential.d.ts +35 -0
- package/dist/utils/individual-onboarding-acceptance-credential.js +55 -0
- package/dist/utils/individual-smart.d.ts +59 -0
- package/dist/utils/individual-smart.js +135 -0
- package/dist/utils/medication-statement-entry-editor.d.ts +90 -0
- package/dist/utils/medication-statement-entry-editor.js +94 -0
- package/dist/utils/observation-component-entry-editor.d.ts +57 -0
- package/dist/utils/observation-component-entry-editor.js +105 -0
- package/dist/utils/observation-entry-editor.d.ts +29 -0
- package/dist/utils/observation-entry-editor.js +68 -0
- package/dist/utils/procedure-entry-editor.d.ts +71 -0
- package/dist/utils/procedure-entry-editor.js +75 -0
- package/dist/utils/professional-smart.d.ts +87 -0
- package/dist/utils/professional-smart.js +118 -4
- package/dist/utils/same-as.d.ts +40 -0
- package/dist/utils/same-as.js +72 -0
- package/dist/utils/unified-health-id.d.ts +51 -0
- package/dist/utils/unified-health-id.js +92 -0
- package/dist/utils/vital-sign-day-batch.d.ts +25 -0
- package/dist/utils/vital-sign-day-batch.js +115 -0
- package/dist/utils/vital-sign-entry-editor.d.ts +61 -0
- package/dist/utils/vital-sign-entry-editor.js +152 -0
- package/package.json +2 -1
package/dist/utils/same-as.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
+
import { normalizePhone } from './consent.js';
|
|
2
3
|
import { encodeMultibase58btc } from './multibase58.js';
|
|
3
4
|
/**
|
|
4
5
|
* Builds the ICA-compatible multibase58(multihash(sha3-256)) payload for a
|
|
@@ -28,6 +29,12 @@ function looksLikeEmail(value) {
|
|
|
28
29
|
const trimmed = value.trim();
|
|
29
30
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed);
|
|
30
31
|
}
|
|
32
|
+
function splitCommaSeparatedValues(value) {
|
|
33
|
+
return String(value || '')
|
|
34
|
+
.split(',')
|
|
35
|
+
.map((candidate) => candidate.trim())
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
}
|
|
31
38
|
/**
|
|
32
39
|
* Normalizes a public controller alias into the same representation used by
|
|
33
40
|
* ICA for `credentialSubject.sameAs` and `controller.sameAs`.
|
|
@@ -64,6 +71,71 @@ export function normalizeSameAsHash(value) {
|
|
|
64
71
|
}
|
|
65
72
|
return trimmed;
|
|
66
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Normalizes one or more `sameAs` aliases into one deduplicated array.
|
|
76
|
+
*
|
|
77
|
+
* Input forms:
|
|
78
|
+
* - one plain string
|
|
79
|
+
* - one CSV string
|
|
80
|
+
* - one array of plain/CSV strings
|
|
81
|
+
*
|
|
82
|
+
* Normalization rules:
|
|
83
|
+
* - whitespace-only entries are discarded
|
|
84
|
+
* - emails are hashed into ICA-compatible `urn:multibase:z...`
|
|
85
|
+
* - already-normalized `urn:multibase:z...` values are preserved
|
|
86
|
+
* - duplicate normalized entries are removed while preserving order
|
|
87
|
+
*
|
|
88
|
+
* @param value Canonical sameAs source value(s).
|
|
89
|
+
*/
|
|
90
|
+
export function normalizeSameAsHashList(value) {
|
|
91
|
+
const candidates = Array.isArray(value)
|
|
92
|
+
? value.flatMap((entry) => splitCommaSeparatedValues(entry))
|
|
93
|
+
: splitCommaSeparatedValues(String(value || ''));
|
|
94
|
+
const normalized = candidates
|
|
95
|
+
.map((candidate) => normalizeSameAsHash(candidate))
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
return [...new Set(normalized)];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Joins one or more normalized `sameAs` aliases into the current flat CSV
|
|
101
|
+
* storage form used by some shared VC/profile helpers.
|
|
102
|
+
*
|
|
103
|
+
* @param value Canonical sameAs source value(s).
|
|
104
|
+
*/
|
|
105
|
+
export function normalizeSameAsHashCsv(value) {
|
|
106
|
+
return normalizeSameAsHashList(value).join(',');
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Normalizes one public telephone alias into the same hashed
|
|
110
|
+
* `urn:multibase:z...` representation used for ICA-compatible public
|
|
111
|
+
* continuity identifiers, while keeping the telephone claim separate from
|
|
112
|
+
* `sameAs`.
|
|
113
|
+
*
|
|
114
|
+
* Rules:
|
|
115
|
+
* - empty input becomes `''`
|
|
116
|
+
* - `urn:multibase:z...` stays unchanged
|
|
117
|
+
* - bare `z...` becomes `urn:multibase:z...`
|
|
118
|
+
* - plain phone-like values are compacted through `normalizePhone(...)`
|
|
119
|
+
* before hashing
|
|
120
|
+
*
|
|
121
|
+
* @param value Plain or already-normalized public telephone alias.
|
|
122
|
+
*/
|
|
123
|
+
export function normalizeTelephoneHash(value) {
|
|
124
|
+
const trimmed = String(value || '').trim();
|
|
125
|
+
if (!trimmed)
|
|
126
|
+
return '';
|
|
127
|
+
if (trimmed.toLowerCase().startsWith('urn:multibase:')) {
|
|
128
|
+
const suffix = trimmed.slice('urn:multibase:'.length).trim();
|
|
129
|
+
return looksLikeBase58Multibase(suffix) ? `urn:multibase:${suffix}` : trimmed;
|
|
130
|
+
}
|
|
131
|
+
if (looksLikeBase58Multibase(trimmed)) {
|
|
132
|
+
return `urn:multibase:${trimmed}`;
|
|
133
|
+
}
|
|
134
|
+
const normalizedPhone = normalizePhone(trimmed);
|
|
135
|
+
if (!normalizedPhone)
|
|
136
|
+
return '';
|
|
137
|
+
return `urn:multibase:${multibase58MultihashSha3_256(normalizedPhone)}`;
|
|
138
|
+
}
|
|
67
139
|
/**
|
|
68
140
|
* Compares two public controller aliases after ICA-compatible normalization.
|
|
69
141
|
*
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes the Damm check digit for one decimal payload.
|
|
3
|
+
*
|
|
4
|
+
* Canonical input:
|
|
5
|
+
* - decimal digits only
|
|
6
|
+
*
|
|
7
|
+
* Algorithm:
|
|
8
|
+
* - Damm check digit over the full normalized sequence
|
|
9
|
+
*
|
|
10
|
+
* Compatibility:
|
|
11
|
+
* - this helper is intentionally strict about the normalized payload so the
|
|
12
|
+
* same digits always produce the same digit, regardless of presentation
|
|
13
|
+
*/
|
|
14
|
+
export declare function computeDammCheckDigit(payloadDigits: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Computes the provider-scoped Unified Health ID control digit.
|
|
17
|
+
*
|
|
18
|
+
* Canonical input:
|
|
19
|
+
* - `providerId` normalized to digits only
|
|
20
|
+
* - `personalIdWithoutCheckDigit` normalized to digits only
|
|
21
|
+
* - the check digit is computed over `providerId + personalIdWithoutCheckDigit`
|
|
22
|
+
*
|
|
23
|
+
* Algorithm:
|
|
24
|
+
* - Damm check digit over the concatenated normalized digits
|
|
25
|
+
*
|
|
26
|
+
* Compatibility:
|
|
27
|
+
* - separators such as `:` and `-` are ignored
|
|
28
|
+
* - the same personal payload can produce a different digit in another
|
|
29
|
+
* provider because the provider digits are part of the normalized sequence
|
|
30
|
+
*
|
|
31
|
+
* @param providerId Provider-scoped numeric identifier. Presentation separators are ignored.
|
|
32
|
+
* @param personalIdWithoutCheckDigit Personal identifier payload without its final control digit.
|
|
33
|
+
* @returns One decimal check digit as a string.
|
|
34
|
+
*/
|
|
35
|
+
export declare function computeUnifiedHealthIdCheckDigit(providerId: string, personalIdWithoutCheckDigit: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Builds the provider-scoped Unified Health ID personal digits including the
|
|
38
|
+
* final control digit.
|
|
39
|
+
*
|
|
40
|
+
* Canonical input:
|
|
41
|
+
* - `providerId` normalized to digits only
|
|
42
|
+
* - `personalIdWithoutCheckDigit` normalized to digits only
|
|
43
|
+
*
|
|
44
|
+
* Output:
|
|
45
|
+
* - the normalized personal digits followed by the computed control digit
|
|
46
|
+
*
|
|
47
|
+
* @param providerId Provider-scoped numeric identifier. Presentation separators are ignored.
|
|
48
|
+
* @param personalIdWithoutCheckDigit Personal identifier payload without its final control digit.
|
|
49
|
+
* @returns The normalized personal digits plus the final control digit.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildUnifiedHealthIdPersonalDigits(providerId: string, personalIdWithoutCheckDigit: string): string;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
/**
|
|
3
|
+
* Computes the Damm check digit for one decimal payload.
|
|
4
|
+
*
|
|
5
|
+
* Canonical input:
|
|
6
|
+
* - decimal digits only
|
|
7
|
+
*
|
|
8
|
+
* Algorithm:
|
|
9
|
+
* - Damm check digit over the full normalized sequence
|
|
10
|
+
*
|
|
11
|
+
* Compatibility:
|
|
12
|
+
* - this helper is intentionally strict about the normalized payload so the
|
|
13
|
+
* same digits always produce the same digit, regardless of presentation
|
|
14
|
+
*/
|
|
15
|
+
export function computeDammCheckDigit(payloadDigits) {
|
|
16
|
+
const normalized = normalizeDigitsOnly(payloadDigits, 'payloadDigits');
|
|
17
|
+
const table = [
|
|
18
|
+
[0, 3, 1, 7, 5, 9, 8, 6, 4, 2],
|
|
19
|
+
[7, 0, 9, 2, 1, 5, 4, 8, 6, 3],
|
|
20
|
+
[4, 2, 0, 6, 8, 7, 1, 3, 5, 9],
|
|
21
|
+
[1, 7, 5, 0, 9, 8, 3, 4, 2, 6],
|
|
22
|
+
[6, 1, 2, 3, 0, 4, 5, 9, 7, 8],
|
|
23
|
+
[3, 6, 7, 4, 2, 0, 9, 5, 8, 1],
|
|
24
|
+
[5, 8, 6, 9, 7, 2, 0, 1, 3, 4],
|
|
25
|
+
[8, 9, 4, 5, 3, 6, 2, 0, 1, 7],
|
|
26
|
+
[9, 4, 3, 8, 6, 1, 7, 2, 0, 5],
|
|
27
|
+
[2, 5, 8, 1, 4, 3, 6, 7, 9, 0],
|
|
28
|
+
];
|
|
29
|
+
let interim = 0;
|
|
30
|
+
for (const digit of normalized) {
|
|
31
|
+
interim = table[interim][Number(digit)];
|
|
32
|
+
}
|
|
33
|
+
return String(interim);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Computes the provider-scoped Unified Health ID control digit.
|
|
37
|
+
*
|
|
38
|
+
* Canonical input:
|
|
39
|
+
* - `providerId` normalized to digits only
|
|
40
|
+
* - `personalIdWithoutCheckDigit` normalized to digits only
|
|
41
|
+
* - the check digit is computed over `providerId + personalIdWithoutCheckDigit`
|
|
42
|
+
*
|
|
43
|
+
* Algorithm:
|
|
44
|
+
* - Damm check digit over the concatenated normalized digits
|
|
45
|
+
*
|
|
46
|
+
* Compatibility:
|
|
47
|
+
* - separators such as `:` and `-` are ignored
|
|
48
|
+
* - the same personal payload can produce a different digit in another
|
|
49
|
+
* provider because the provider digits are part of the normalized sequence
|
|
50
|
+
*
|
|
51
|
+
* @param providerId Provider-scoped numeric identifier. Presentation separators are ignored.
|
|
52
|
+
* @param personalIdWithoutCheckDigit Personal identifier payload without its final control digit.
|
|
53
|
+
* @returns One decimal check digit as a string.
|
|
54
|
+
*/
|
|
55
|
+
export function computeUnifiedHealthIdCheckDigit(providerId, personalIdWithoutCheckDigit) {
|
|
56
|
+
const normalizedProviderId = normalizeDigitsOnly(providerId, 'providerId');
|
|
57
|
+
const normalizedPersonalId = normalizeDigitsOnly(personalIdWithoutCheckDigit, 'personalIdWithoutCheckDigit');
|
|
58
|
+
return computeDammCheckDigit(`${normalizedProviderId}${normalizedPersonalId}`);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Builds the provider-scoped Unified Health ID personal digits including the
|
|
62
|
+
* final control digit.
|
|
63
|
+
*
|
|
64
|
+
* Canonical input:
|
|
65
|
+
* - `providerId` normalized to digits only
|
|
66
|
+
* - `personalIdWithoutCheckDigit` normalized to digits only
|
|
67
|
+
*
|
|
68
|
+
* Output:
|
|
69
|
+
* - the normalized personal digits followed by the computed control digit
|
|
70
|
+
*
|
|
71
|
+
* @param providerId Provider-scoped numeric identifier. Presentation separators are ignored.
|
|
72
|
+
* @param personalIdWithoutCheckDigit Personal identifier payload without its final control digit.
|
|
73
|
+
* @returns The normalized personal digits plus the final control digit.
|
|
74
|
+
*/
|
|
75
|
+
export function buildUnifiedHealthIdPersonalDigits(providerId, personalIdWithoutCheckDigit) {
|
|
76
|
+
const normalizedPersonalId = normalizeDigitsOnly(personalIdWithoutCheckDigit, 'personalIdWithoutCheckDigit');
|
|
77
|
+
return `${normalizedPersonalId}${computeUnifiedHealthIdCheckDigit(providerId, normalizedPersonalId)}`;
|
|
78
|
+
}
|
|
79
|
+
function normalizeDigitsOnly(value, name) {
|
|
80
|
+
const normalized = String(value || '').trim();
|
|
81
|
+
if (!normalized) {
|
|
82
|
+
throw new Error(`${name} must not be empty`);
|
|
83
|
+
}
|
|
84
|
+
if (!/^[0-9:\-\s]+$/.test(normalized)) {
|
|
85
|
+
throw new Error(`${name} must contain only digits, spaces, colons, or hyphens`);
|
|
86
|
+
}
|
|
87
|
+
const digits = normalized.replace(/\D/g, '');
|
|
88
|
+
if (!digits) {
|
|
89
|
+
throw new Error(`${name} must contain at least one digit`);
|
|
90
|
+
}
|
|
91
|
+
return digits;
|
|
92
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BundleEntry, BundleJsonApi } from '../models/bundle';
|
|
2
|
+
export declare const VitalSignDayBatchClaim: Readonly<{
|
|
3
|
+
readonly Identifier: "VitalSignDayBatch.identifier";
|
|
4
|
+
readonly Subject: "VitalSignDayBatch.subject";
|
|
5
|
+
readonly Actor: "VitalSignDayBatch.actor";
|
|
6
|
+
readonly Day: "VitalSignDayBatch.day";
|
|
7
|
+
readonly CreatedAtTimestamp: "VitalSignDayBatch.createdAtTimestamp";
|
|
8
|
+
}>;
|
|
9
|
+
export type VitalSignDayBatchEntryInput = Readonly<{
|
|
10
|
+
subject: string;
|
|
11
|
+
actor: string;
|
|
12
|
+
date: string | Date;
|
|
13
|
+
createdAtTimestamp?: number;
|
|
14
|
+
entry?: BundleEntry;
|
|
15
|
+
bundle?: BundleJsonApi<BundleEntry>;
|
|
16
|
+
}>;
|
|
17
|
+
export type VitalSignDayBatchResult = Readonly<{
|
|
18
|
+
batchId: string;
|
|
19
|
+
day: string;
|
|
20
|
+
reused: boolean;
|
|
21
|
+
bundle: BundleJsonApi<BundleEntry>;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function buildVitalSignDayBatchClaims(input: Pick<VitalSignDayBatchEntryInput, 'subject' | 'actor' | 'date' | 'createdAtTimestamp'>): Record<string, unknown>;
|
|
24
|
+
export declare function resolveVitalSignDayBatchId(input: VitalSignDayBatchEntryInput): VitalSignDayBatchResult;
|
|
25
|
+
export declare function openOrCreateVitalSignDayBatchBundle(input: VitalSignDayBatchEntryInput): BundleJsonApi<BundleEntry>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { createCanonicalIdentifierUrn } from './bundle-editor-helpers.js';
|
|
3
|
+
import { BundleQuery } from './bundle-query.js';
|
|
4
|
+
export const VitalSignDayBatchClaim = Object.freeze({
|
|
5
|
+
Identifier: 'VitalSignDayBatch.identifier',
|
|
6
|
+
Subject: 'VitalSignDayBatch.subject',
|
|
7
|
+
Actor: 'VitalSignDayBatch.actor',
|
|
8
|
+
Day: 'VitalSignDayBatch.day',
|
|
9
|
+
CreatedAtTimestamp: 'VitalSignDayBatch.createdAtTimestamp',
|
|
10
|
+
});
|
|
11
|
+
export function buildVitalSignDayBatchClaims(input) {
|
|
12
|
+
const day = resolveDayKey(input.date);
|
|
13
|
+
const claims = {
|
|
14
|
+
[VitalSignDayBatchClaim.Subject]: asTrimmedString(input.subject),
|
|
15
|
+
[VitalSignDayBatchClaim.Actor]: asTrimmedString(input.actor),
|
|
16
|
+
[VitalSignDayBatchClaim.Day]: day,
|
|
17
|
+
};
|
|
18
|
+
if (typeof input.createdAtTimestamp === 'number' && Number.isFinite(input.createdAtTimestamp)) {
|
|
19
|
+
claims[VitalSignDayBatchClaim.CreatedAtTimestamp] = input.createdAtTimestamp;
|
|
20
|
+
}
|
|
21
|
+
return claims;
|
|
22
|
+
}
|
|
23
|
+
export function resolveVitalSignDayBatchId(input) {
|
|
24
|
+
const baseBundle = cloneBundle(input.bundle || createEmptyBundle());
|
|
25
|
+
const day = resolveDayKey(input.date);
|
|
26
|
+
const dayRange = resolveDayRange(day);
|
|
27
|
+
const existingClaims = asRecord(baseBundle.meta?.claims);
|
|
28
|
+
const query = new BundleQuery(baseBundle);
|
|
29
|
+
const dayEntryIds = query.getResourceIds({ dateFrom: dayRange.from, dateTo: dayRange.to });
|
|
30
|
+
const dayEntries = query.getResourceEntriesByIds(dayEntryIds);
|
|
31
|
+
const matchingDayEntry = dayEntries.find((entry) => matchesBatchOwner(entry, input.subject, input.actor));
|
|
32
|
+
const sameOwner = matchesBatchOwnerClaims(existingClaims, input.subject, input.actor);
|
|
33
|
+
const sameDay = asTrimmedString(existingClaims[VitalSignDayBatchClaim.Day]) === day;
|
|
34
|
+
const existingBatchId = asTrimmedString(baseBundle.id || existingClaims[VitalSignDayBatchClaim.Identifier]);
|
|
35
|
+
const reused = Boolean(existingBatchId && sameOwner && sameDay && (dayEntryIds.length > 0 || matchingDayEntry));
|
|
36
|
+
const batchId = reused ? existingBatchId : createCanonicalIdentifierUrn();
|
|
37
|
+
const bundle = {
|
|
38
|
+
...baseBundle,
|
|
39
|
+
id: batchId,
|
|
40
|
+
meta: {
|
|
41
|
+
...(baseBundle.meta || {}),
|
|
42
|
+
claims: {
|
|
43
|
+
...existingClaims,
|
|
44
|
+
...buildVitalSignDayBatchClaims(input),
|
|
45
|
+
[VitalSignDayBatchClaim.Identifier]: batchId,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
data: [...(baseBundle.data || [])],
|
|
49
|
+
};
|
|
50
|
+
if (input.entry) {
|
|
51
|
+
bundle.data.push(cloneEntry(input.entry));
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
batchId,
|
|
55
|
+
day,
|
|
56
|
+
reused,
|
|
57
|
+
bundle,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function openOrCreateVitalSignDayBatchBundle(input) {
|
|
61
|
+
return resolveVitalSignDayBatchId(input).bundle;
|
|
62
|
+
}
|
|
63
|
+
function matchesBatchOwner(entry, subject, actor) {
|
|
64
|
+
const claims = asRecord(entry.resource?.meta?.claims || entry.meta?.claims);
|
|
65
|
+
return matchesBatchOwnerClaims(claims, subject, actor);
|
|
66
|
+
}
|
|
67
|
+
function matchesBatchOwnerClaims(claims, subject, actor) {
|
|
68
|
+
return asTrimmedString(claims[VitalSignDayBatchClaim.Subject]) === asTrimmedString(subject)
|
|
69
|
+
&& asTrimmedString(claims[VitalSignDayBatchClaim.Actor]) === asTrimmedString(actor);
|
|
70
|
+
}
|
|
71
|
+
function resolveDayKey(value) {
|
|
72
|
+
if (value instanceof Date) {
|
|
73
|
+
return value.toISOString().slice(0, 10);
|
|
74
|
+
}
|
|
75
|
+
const raw = asTrimmedString(value);
|
|
76
|
+
if (!raw) {
|
|
77
|
+
return new Date().toISOString().slice(0, 10);
|
|
78
|
+
}
|
|
79
|
+
const parsed = Date.parse(raw);
|
|
80
|
+
if (Number.isNaN(parsed)) {
|
|
81
|
+
return raw.slice(0, 10);
|
|
82
|
+
}
|
|
83
|
+
return new Date(parsed).toISOString().slice(0, 10);
|
|
84
|
+
}
|
|
85
|
+
function resolveDayRange(day) {
|
|
86
|
+
return {
|
|
87
|
+
from: `${day}T00:00:00.000Z`,
|
|
88
|
+
to: `${day}T23:59:59.999Z`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function asTrimmedString(value) {
|
|
92
|
+
if (value === undefined || value === null) {
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
return String(value).trim();
|
|
96
|
+
}
|
|
97
|
+
function asRecord(value) {
|
|
98
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
function cloneBundle(bundle) {
|
|
104
|
+
return JSON.parse(JSON.stringify(bundle));
|
|
105
|
+
}
|
|
106
|
+
function cloneEntry(entry) {
|
|
107
|
+
return JSON.parse(JSON.stringify(entry));
|
|
108
|
+
}
|
|
109
|
+
function createEmptyBundle() {
|
|
110
|
+
return {
|
|
111
|
+
resourceType: 'Bundle',
|
|
112
|
+
type: 'collection',
|
|
113
|
+
data: [],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File discipline note:
|
|
3
|
+
* - Read `ARCHITECTURE.md` and `CONTRIBUTING.md` before changing this module.
|
|
4
|
+
* - Keep exactly one exported class per file.
|
|
5
|
+
* - Keep this file focused on one typed editor surface.
|
|
6
|
+
* - Move shared helpers to reusable helper/base modules instead of duplicating logic here.
|
|
7
|
+
*/
|
|
8
|
+
import { type CodingDescriptor } from '../constants/vital-signs';
|
|
9
|
+
import { ObservationComponentEntryEditor } from './observation-component-entry-editor';
|
|
10
|
+
/**
|
|
11
|
+
* Typed editor for one staged VitalSign resource entry.
|
|
12
|
+
*
|
|
13
|
+
* Use this surface when the frontend needs a simple authoring path for common
|
|
14
|
+
* measurements such as heart rate, temperature, or blood pressure.
|
|
15
|
+
*/
|
|
16
|
+
export declare class VitalSignEntryEditor extends ObservationComponentEntryEditor {
|
|
17
|
+
/** Overrides the canonical identifier when import or migration logic needs one explicit public id. */
|
|
18
|
+
setIdentifier(identifier?: string | null): this;
|
|
19
|
+
/** Returns the canonical identifier currently exposed for this vital-sign entry. */
|
|
20
|
+
getIdentifier(): string | undefined;
|
|
21
|
+
/** Ensures the entry has one synchronized canonical identifier across public claim, `resource.id`, and `fullUrl`. */
|
|
22
|
+
ensureIdentifier(): string;
|
|
23
|
+
/** Sets the subject/patient reference for this vital-sign observation. */
|
|
24
|
+
setSubject(subject: string): this;
|
|
25
|
+
/** Returns the subject/patient reference for this vital-sign observation. */
|
|
26
|
+
getSubject(): string | undefined;
|
|
27
|
+
/** Sets the observation status. */
|
|
28
|
+
setStatus(status: string): this;
|
|
29
|
+
/** Returns the observation status. */
|
|
30
|
+
getStatus(): string | undefined;
|
|
31
|
+
/** Sets the observation category, usually one of the shared vital-sign category descriptors. */
|
|
32
|
+
setCategory(category: CodingDescriptor | string): this;
|
|
33
|
+
/** Returns the observation category token. */
|
|
34
|
+
getCategory(): string | undefined;
|
|
35
|
+
/** Sets the observation date/effective time. */
|
|
36
|
+
setDate(date: string): this;
|
|
37
|
+
/** Returns the observation date/effective time. */
|
|
38
|
+
getDate(): string | undefined;
|
|
39
|
+
/** Sets one free-text note for the vital sign. */
|
|
40
|
+
setNote(note: string): this;
|
|
41
|
+
/** Returns the free-text note for the vital sign. */
|
|
42
|
+
getNote(): string | undefined;
|
|
43
|
+
/** Seeds the observation code and optional quantity unit for the selected vital-sign family. */
|
|
44
|
+
setVitalSignType(code: CodingDescriptor, unit?: CodingDescriptor): this;
|
|
45
|
+
/** Convenience helper for heart-rate authoring using the shared code/unit catalog. */
|
|
46
|
+
setHeartRate(value: number): this;
|
|
47
|
+
/** Returns the heart-rate numeric value. */
|
|
48
|
+
getHeartRate(): number | undefined;
|
|
49
|
+
/** Convenience helper for body-temperature authoring using the shared code/unit catalog. */
|
|
50
|
+
setBodyTemperature(value: number): this;
|
|
51
|
+
/** Returns the body-temperature numeric value. */
|
|
52
|
+
getBodyTemperature(): number | undefined;
|
|
53
|
+
/** Convenience helper for systolic blood-pressure authoring. */
|
|
54
|
+
setSystolicBloodPressure(value: number): this;
|
|
55
|
+
/** Returns the systolic blood-pressure numeric value. */
|
|
56
|
+
getSystolicBloodPressure(): number | undefined;
|
|
57
|
+
/** Convenience helper for diastolic blood-pressure authoring. */
|
|
58
|
+
setDiastolicBloodPressure(value: number): this;
|
|
59
|
+
/** Returns the diastolic blood-pressure numeric value. */
|
|
60
|
+
getDiastolicBloodPressure(): number | undefined;
|
|
61
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File discipline note:
|
|
3
|
+
* - Read `ARCHITECTURE.md` and `CONTRIBUTING.md` before changing this module.
|
|
4
|
+
* - Keep exactly one exported class per file.
|
|
5
|
+
* - Keep this file focused on one typed editor surface.
|
|
6
|
+
* - Move shared helpers to reusable helper/base modules instead of duplicating logic here.
|
|
7
|
+
*/
|
|
8
|
+
import { ObservationCategoryCodes, VitalSignsCodes, VitalSignsUnits } from '../constants/vital-signs.js';
|
|
9
|
+
import { ObservationClaim } from '../models/interoperable-claims/observation-claims.js';
|
|
10
|
+
import { BundleEditableResourceTypes } from '../models/bundle-editor-types.js';
|
|
11
|
+
import { createCanonicalIdentifierUrn, normalizeOptionalIdentifier } from './bundle-editor-helpers.js';
|
|
12
|
+
import { ObservationComponentEntryEditor } from './observation-component-entry-editor.js';
|
|
13
|
+
import { registerBundleEntryEditor } from './bundle-editor-registry.js';
|
|
14
|
+
/**
|
|
15
|
+
* Typed editor for one staged VitalSign resource entry.
|
|
16
|
+
*
|
|
17
|
+
* Use this surface when the frontend needs a simple authoring path for common
|
|
18
|
+
* measurements such as heart rate, temperature, or blood pressure.
|
|
19
|
+
*/
|
|
20
|
+
export class VitalSignEntryEditor extends ObservationComponentEntryEditor {
|
|
21
|
+
/** Overrides the canonical identifier when import or migration logic needs one explicit public id. */
|
|
22
|
+
setIdentifier(identifier) {
|
|
23
|
+
const normalized = normalizeOptionalIdentifier(identifier);
|
|
24
|
+
if (!normalized) {
|
|
25
|
+
this.removeClaim(ObservationClaim.Identifier);
|
|
26
|
+
this.setResourceId(undefined);
|
|
27
|
+
this.setFullUrl(undefined);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
this.setClaim(ObservationClaim.Identifier, normalized);
|
|
31
|
+
this.setResourceId(normalized);
|
|
32
|
+
this.setFullUrl(normalized);
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
/** Returns the canonical identifier currently exposed for this vital-sign entry. */
|
|
36
|
+
getIdentifier() {
|
|
37
|
+
return normalizeOptionalIdentifier(this.getClaim(ObservationClaim.Identifier)
|
|
38
|
+
|| this.getResourceId()
|
|
39
|
+
|| this.getFullUrl());
|
|
40
|
+
}
|
|
41
|
+
/** Ensures the entry has one synchronized canonical identifier across public claim, `resource.id`, and `fullUrl`. */
|
|
42
|
+
ensureIdentifier() {
|
|
43
|
+
const existing = this.getIdentifier();
|
|
44
|
+
if (existing)
|
|
45
|
+
return existing;
|
|
46
|
+
const generated = createCanonicalIdentifierUrn();
|
|
47
|
+
this.setIdentifier(generated);
|
|
48
|
+
return generated;
|
|
49
|
+
}
|
|
50
|
+
/** Sets the subject/patient reference for this vital-sign observation. */
|
|
51
|
+
setSubject(subject) {
|
|
52
|
+
const normalized = String(subject).trim();
|
|
53
|
+
this.setClaim(ObservationClaim.Subject, normalized);
|
|
54
|
+
this.setClaim(ObservationClaim.Patient, normalized);
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
/** Returns the subject/patient reference for this vital-sign observation. */
|
|
58
|
+
getSubject() {
|
|
59
|
+
return normalizeOptionalIdentifier(this.getClaim(ObservationClaim.Subject)
|
|
60
|
+
|| this.getClaim(ObservationClaim.Patient));
|
|
61
|
+
}
|
|
62
|
+
/** Sets the observation status. */
|
|
63
|
+
setStatus(status) {
|
|
64
|
+
return this.setClaim(ObservationClaim.Status, String(status).trim());
|
|
65
|
+
}
|
|
66
|
+
/** Returns the observation status. */
|
|
67
|
+
getStatus() {
|
|
68
|
+
return normalizeOptionalIdentifier(this.getClaim(ObservationClaim.Status));
|
|
69
|
+
}
|
|
70
|
+
/** Sets the observation category, usually one of the shared vital-sign category descriptors. */
|
|
71
|
+
setCategory(category) {
|
|
72
|
+
const normalized = typeof category === 'string' ? category.trim() : category.claim;
|
|
73
|
+
return this.setClaim(ObservationClaim.Category, normalized);
|
|
74
|
+
}
|
|
75
|
+
/** Returns the observation category token. */
|
|
76
|
+
getCategory() {
|
|
77
|
+
return normalizeOptionalIdentifier(this.getClaim(ObservationClaim.Category));
|
|
78
|
+
}
|
|
79
|
+
/** Sets the observation date/effective time. */
|
|
80
|
+
setDate(date) {
|
|
81
|
+
const normalized = String(date).trim();
|
|
82
|
+
this.setClaim(ObservationClaim.Date, normalized);
|
|
83
|
+
this.setClaim(ObservationClaim.EffectiveDateTime, normalized);
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
/** Returns the observation date/effective time. */
|
|
87
|
+
getDate() {
|
|
88
|
+
return normalizeOptionalIdentifier(this.getClaim(ObservationClaim.Date)
|
|
89
|
+
|| this.getClaim(ObservationClaim.EffectiveDateTime));
|
|
90
|
+
}
|
|
91
|
+
/** Sets one free-text note for the vital sign. */
|
|
92
|
+
setNote(note) {
|
|
93
|
+
return this.setClaim(ObservationClaim.Note, String(note).trim());
|
|
94
|
+
}
|
|
95
|
+
/** Returns the free-text note for the vital sign. */
|
|
96
|
+
getNote() {
|
|
97
|
+
return normalizeOptionalIdentifier(this.getClaim(ObservationClaim.Note));
|
|
98
|
+
}
|
|
99
|
+
/** Seeds the observation code and optional quantity unit for the selected vital-sign family. */
|
|
100
|
+
setVitalSignType(code, unit) {
|
|
101
|
+
this.setCategory(ObservationCategoryCodes.VitalSigns);
|
|
102
|
+
this.setStatus(this.getStatus() || 'final');
|
|
103
|
+
this.setCode(code);
|
|
104
|
+
this.setCodeSystem(code.system);
|
|
105
|
+
this.setCodeValue(code.code);
|
|
106
|
+
if (code.display) {
|
|
107
|
+
this.setCodeDisplay(code.display);
|
|
108
|
+
this.setCodeTextLocal(code.display);
|
|
109
|
+
}
|
|
110
|
+
if (unit) {
|
|
111
|
+
this.setValueQuantityUnit(unit);
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/** Convenience helper for heart-rate authoring using the shared code/unit catalog. */
|
|
116
|
+
setHeartRate(value) {
|
|
117
|
+
this.setVitalSignType(VitalSignsCodes.HeartRate, VitalSignsUnits.BeatsPerMinute);
|
|
118
|
+
return this.setValueQuantityNumber(value);
|
|
119
|
+
}
|
|
120
|
+
/** Returns the heart-rate numeric value. */
|
|
121
|
+
getHeartRate() {
|
|
122
|
+
return this.getValueQuantityNumber();
|
|
123
|
+
}
|
|
124
|
+
/** Convenience helper for body-temperature authoring using the shared code/unit catalog. */
|
|
125
|
+
setBodyTemperature(value) {
|
|
126
|
+
this.setVitalSignType(VitalSignsCodes.BodyTemperature, VitalSignsUnits.Celsius);
|
|
127
|
+
return this.setValueQuantityNumber(value);
|
|
128
|
+
}
|
|
129
|
+
/** Returns the body-temperature numeric value. */
|
|
130
|
+
getBodyTemperature() {
|
|
131
|
+
return this.getValueQuantityNumber();
|
|
132
|
+
}
|
|
133
|
+
/** Convenience helper for systolic blood-pressure authoring. */
|
|
134
|
+
setSystolicBloodPressure(value) {
|
|
135
|
+
this.setVitalSignType(VitalSignsCodes.SystolicBloodPressure, VitalSignsUnits.MillimeterOfMercury);
|
|
136
|
+
return this.setValueQuantityNumber(value);
|
|
137
|
+
}
|
|
138
|
+
/** Returns the systolic blood-pressure numeric value. */
|
|
139
|
+
getSystolicBloodPressure() {
|
|
140
|
+
return this.getValueQuantityNumber();
|
|
141
|
+
}
|
|
142
|
+
/** Convenience helper for diastolic blood-pressure authoring. */
|
|
143
|
+
setDiastolicBloodPressure(value) {
|
|
144
|
+
this.setVitalSignType(VitalSignsCodes.DiastolicBloodPressure, VitalSignsUnits.MillimeterOfMercury);
|
|
145
|
+
return this.setValueQuantityNumber(value);
|
|
146
|
+
}
|
|
147
|
+
/** Returns the diastolic blood-pressure numeric value. */
|
|
148
|
+
getDiastolicBloodPressure() {
|
|
149
|
+
return this.getValueQuantityNumber();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
registerBundleEntryEditor(BundleEditableResourceTypes.vitalSign, VitalSignEntryEditor);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdc-common-utils-ts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"@stablelib/utf8": "^1.0.2",
|
|
12
12
|
"base-x": "^4.0.0",
|
|
13
13
|
"gdc-common-utils-ts": "^2.0.1",
|
|
14
|
+
"jose": "^4.15.9",
|
|
14
15
|
"pako": "^2.1.0"
|
|
15
16
|
},
|
|
16
17
|
"scripts": {
|