gdc-common-utils-ts 2.0.18 → 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 -1
- 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.d.ts +11 -0
- package/dist/examples/bundle-didcomm-payload.js +15 -0
- package/dist/examples/communication-attached-bundle-session.js +28 -27
- package/dist/examples/communication-didcomm-payload.d.ts +39 -0
- package/dist/examples/communication-didcomm-payload.js +44 -0
- 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/index.d.ts +4 -0
- package/dist/examples/index.js +4 -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.d.ts +45 -0
- package/dist/examples/profile-manager-mem.js +30 -0
- package/dist/examples/related-person.d.ts +14 -0
- package/dist/examples/related-person.js +18 -4
- package/dist/examples/shared.d.ts +13 -3
- package/dist/examples/shared.js +14 -4
- package/dist/examples/vital-signs.js +1 -1
- package/dist/examples/wallet-mem.d.ts +38 -0
- package/dist/examples/wallet-mem.js +42 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interfaces/IProfileOutboxRepository.d.ts +36 -0
- package/dist/interfaces/IProfileOutboxRepository.js +2 -0
- package/dist/interfaces/IWallet.d.ts +74 -0
- package/dist/interfaces/IWallet.js +2 -0
- package/dist/interfaces/IWalletQueue.d.ts +30 -0
- package/dist/interfaces/IWalletQueue.js +2 -0
- package/dist/interfaces/index.d.ts +8 -0
- package/dist/interfaces/index.js +8 -0
- 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 +195 -0
- package/dist/models/communication-attached-bundle-session.js +36 -0
- 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 +5 -0
- package/dist/models/index.js +5 -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 +14 -4
- package/dist/models/interoperable-claims/diagnostic-report-claims.js +13 -3
- 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/models/profile-manager.d.ts +85 -0
- package/dist/models/profile-manager.js +2 -0
- package/dist/models/wallet.d.ts +59 -0
- package/dist/models/wallet.js +22 -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 +78 -0
- package/dist/utils/backend-message-manager-mem.js +238 -0
- package/dist/utils/bundle-didcomm-payload.d.ts +29 -0
- package/dist/utils/bundle-didcomm-payload.js +49 -0
- 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.d.ts +54 -0
- package/dist/utils/communication-attached-bundle-session-helpers.js +519 -0
- package/dist/utils/communication-attached-bundle-session.d.ts +87 -304
- package/dist/utils/communication-attached-bundle-session.js +175 -841
- package/dist/utils/communication-consent-access-editor.d.ts +85 -0
- package/dist/utils/communication-consent-access-editor.js +244 -0
- package/dist/utils/communication-didcomm-payload.d.ts +38 -0
- package/dist/utils/communication-didcomm-payload.js +85 -0
- 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 +15 -0
- package/dist/utils/index.js +15 -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/profile-manager-mem.d.ts +69 -0
- package/dist/utils/profile-manager-mem.js +79 -0
- package/dist/utils/profile-outbox-memory-repository.d.ts +34 -0
- package/dist/utils/profile-outbox-memory-repository.js +57 -0
- 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/dist/utils/wallet-mem.d.ts +93 -0
- package/dist/utils/wallet-mem.js +277 -0
- package/dist/utils/wallet-memory-queue.d.ts +32 -0
- package/dist/utils/wallet-memory-queue.js +82 -0
- package/package.json +2 -1
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
// Copyright 2026 Conectate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
|
|
2
2
|
// Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
|
|
3
3
|
import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
|
|
4
|
-
import { getHealthcareProfessionalRolesBySector, getHealthcareRoleByClaim, HealthcareRoleFamilies, getHealthcareRolesByFamily, getHealthcareSectionsByFamily, getHealthcareSectionByCode, getHealthcareSectionFamilyByCode, HealthcareConsentPurposes, HealthcareCanonicalSectionFamilies, } from '../constants/healthcare.js';
|
|
5
|
-
import { ClaimConsent } from '../models/consent-rule.js';
|
|
6
|
-
import { AllergyIntoleranceClaim } from '../models/interoperable-claims/allergy-intolerance-claims.js';
|
|
7
4
|
import { CommunicationClaim } from '../models/interoperable-claims/communication-claims.js';
|
|
8
|
-
import { ConditionClaim } from '../models/interoperable-claims/condition-claims.js';
|
|
9
5
|
import { DocumentReferenceClaim } from '../models/interoperable-claims/document-reference-claims.js';
|
|
6
|
+
import { BundleEntryClaimsContext, } from '../models/communication-attached-bundle-session.js';
|
|
10
7
|
import { BundleQuery } from './bundle-query.js';
|
|
11
8
|
import { addClaimValues } from '../claims/claim-list-helpers.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
export const ConsentEditorTargetKinds = Object.freeze({
|
|
16
|
-
Section: 'section',
|
|
17
|
-
ResourceType: 'resource-type',
|
|
18
|
-
});
|
|
19
|
-
export const ConsentEditorScopeCodes = Object.freeze({
|
|
20
|
-
Search: 's',
|
|
21
|
-
Read: 'r',
|
|
22
|
-
Create: 'c',
|
|
23
|
-
Update: 'u',
|
|
24
|
-
Delete: 'd',
|
|
25
|
-
});
|
|
9
|
+
import { getCommunicationCategoryList, getCommunicationIdentifier, getCommunicationSubject, getCommunicationText, getCommunicationTopic, setCommunicationCategory, setCommunicationIdentifier, setCommunicationSubject, setCommunicationText, setCommunicationTopic, } from '../claims/claims-helpers-communication.js';
|
|
10
|
+
import { asTrimmedString, cloneBundle, cloneEntry, cloneUnknownValue, createEmptyBundle, encodeBundleToBase64, ensureEntryResource, resolveBundleEntryCanonicalIdValue, resolveBundleEntryIdentifier, resolveContainedDocumentsClaimKey, resolveSubjectFromClaims, runtimeUuid, setIfMissing, validateBundleLike, } from './communication-attached-bundle-session-helpers.js';
|
|
11
|
+
export { BundleEntryClaimsContext, CommunicationClaimsContext, CommunicationAttachmentClaimsContext, ConsentEditorScopeCodes, ConsentEditorTargetKinds, } from '../models/communication-attached-bundle-session.js';
|
|
26
12
|
/**
|
|
27
13
|
* Communication editing session with bundle-in-memory as source of truth.
|
|
28
14
|
*
|
|
@@ -56,18 +42,82 @@ export class CommunicationAttachedBundleSession {
|
|
|
56
42
|
...this.communicationClaims,
|
|
57
43
|
};
|
|
58
44
|
}
|
|
45
|
+
/** Returns the canonical outer Communication identifier. */
|
|
46
|
+
getCommunicationIdentifier() {
|
|
47
|
+
return getCommunicationIdentifier(this.communicationClaims);
|
|
48
|
+
}
|
|
49
|
+
/** Sets the canonical outer Communication identifier. */
|
|
50
|
+
setCommunicationIdentifier(value) {
|
|
51
|
+
this.communicationClaims = setCommunicationIdentifier(this.communicationClaims, value);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
/** Returns the canonical outer Communication subject. */
|
|
55
|
+
getCommunicationSubject() {
|
|
56
|
+
return getCommunicationSubject(this.communicationClaims);
|
|
57
|
+
}
|
|
58
|
+
/** Sets the canonical outer Communication subject. */
|
|
59
|
+
setCommunicationSubject(value) {
|
|
60
|
+
this.communicationClaims = setCommunicationSubject(this.communicationClaims, value);
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
/** Returns the canonical outer Communication categories. */
|
|
64
|
+
getCommunicationCategoryList() {
|
|
65
|
+
return getCommunicationCategoryList(this.communicationClaims);
|
|
66
|
+
}
|
|
67
|
+
/** Replaces the canonical outer Communication categories. */
|
|
68
|
+
setCommunicationCategory(value) {
|
|
69
|
+
this.communicationClaims = setCommunicationCategory(this.communicationClaims, value);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/** Returns the canonical outer Communication topic. */
|
|
73
|
+
getCommunicationTopic() {
|
|
74
|
+
return getCommunicationTopic(this.communicationClaims);
|
|
75
|
+
}
|
|
76
|
+
/** Sets the canonical outer Communication topic. */
|
|
77
|
+
setCommunicationTopic(value) {
|
|
78
|
+
this.communicationClaims = setCommunicationTopic(this.communicationClaims, value);
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
/** Returns the canonical outer Communication text. */
|
|
82
|
+
getCommunicationText() {
|
|
83
|
+
return getCommunicationText(this.communicationClaims);
|
|
84
|
+
}
|
|
85
|
+
/** Sets the canonical outer Communication text. */
|
|
86
|
+
setCommunicationText(value) {
|
|
87
|
+
this.communicationClaims = setCommunicationText(this.communicationClaims, value);
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
59
90
|
/** Returns a deep copy of the current in-memory bundle. */
|
|
60
91
|
getBundleInMemory() {
|
|
61
92
|
return cloneBundle(this.bundleInMemory);
|
|
62
93
|
}
|
|
94
|
+
/** Replaces the attached clinical bundle and resyncs the outer Communication attachment. */
|
|
95
|
+
setAttachedBundle(bundle) {
|
|
96
|
+
validateBundleLike(bundle, this.mode);
|
|
97
|
+
this.bundleInMemory = cloneBundle(bundle);
|
|
98
|
+
this.syncAttachmentFromBundle();
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
/** Alias of `getBundleInMemory()` with communication wording. */
|
|
102
|
+
getAttachedBundle() {
|
|
103
|
+
return this.getBundleInMemory();
|
|
104
|
+
}
|
|
63
105
|
/** Returns the active entry index, or null when no entry is selected. */
|
|
64
106
|
getActiveEntryIndex() {
|
|
65
107
|
return this.activeEntryIndex;
|
|
66
108
|
}
|
|
109
|
+
/** Returns the active entry position, or null when no entry is selected. */
|
|
110
|
+
getActiveEntryPosition() {
|
|
111
|
+
return this.activeEntryIndex;
|
|
112
|
+
}
|
|
67
113
|
/** Alias of `getActiveEntryIndex()` with entry-selection wording. */
|
|
68
114
|
getSelectedEntryIndex() {
|
|
69
115
|
return this.getActiveEntryIndex();
|
|
70
116
|
}
|
|
117
|
+
/** Alias of `getActiveEntryPosition()` with entry-selection wording. */
|
|
118
|
+
getSelectedEntryPosition() {
|
|
119
|
+
return this.getActiveEntryPosition();
|
|
120
|
+
}
|
|
71
121
|
/** Returns a deep copy of the active entry when selected. */
|
|
72
122
|
getActiveEntry() {
|
|
73
123
|
if (this.activeEntryIndex === null) {
|
|
@@ -155,11 +205,14 @@ export class CommunicationAttachedBundleSession {
|
|
|
155
205
|
this.syncAttachmentFromBundle();
|
|
156
206
|
return this;
|
|
157
207
|
}
|
|
158
|
-
/** Selects an active entry by
|
|
208
|
+
/** Selects an active entry by position or fullUrl. */
|
|
159
209
|
selectActiveEntry(selection) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
210
|
+
const requestedPosition = typeof selection.position === 'number'
|
|
211
|
+
? selection.position
|
|
212
|
+
: selection.index;
|
|
213
|
+
if (typeof requestedPosition === 'number') {
|
|
214
|
+
this.assertEntryIndex(requestedPosition);
|
|
215
|
+
this.activeEntryIndex = requestedPosition;
|
|
163
216
|
return this;
|
|
164
217
|
}
|
|
165
218
|
if (selection.fullUrl) {
|
|
@@ -170,7 +223,7 @@ export class CommunicationAttachedBundleSession {
|
|
|
170
223
|
this.activeEntryIndex = foundIndex;
|
|
171
224
|
return this;
|
|
172
225
|
}
|
|
173
|
-
throw new Error('selectActiveEntry requires either
|
|
226
|
+
throw new Error('selectActiveEntry requires either position or fullUrl.');
|
|
174
227
|
}
|
|
175
228
|
/** Alias of `selectActiveEntry()` with entry-selection wording. */
|
|
176
229
|
selectEntry(selection) {
|
|
@@ -200,49 +253,45 @@ export class CommunicationAttachedBundleSession {
|
|
|
200
253
|
return this;
|
|
201
254
|
}
|
|
202
255
|
/**
|
|
203
|
-
*
|
|
256
|
+
* Reusable resource-typed wrapper around `upsertActiveEntry(...)`.
|
|
204
257
|
*
|
|
205
|
-
*
|
|
258
|
+
* Resource-specific helpers should delegate here instead of re-embedding the
|
|
259
|
+
* same `resourceType/fullUrl/type/request/claims` plumbing.
|
|
206
260
|
*/
|
|
207
|
-
|
|
261
|
+
upsertActiveClaimsResourceEntry(resourceType, input) {
|
|
208
262
|
return this.upsertActiveEntry({
|
|
209
|
-
resourceType
|
|
210
|
-
claims:
|
|
263
|
+
resourceType,
|
|
264
|
+
claims: {
|
|
265
|
+
...input.claims,
|
|
266
|
+
},
|
|
211
267
|
fullUrl: input.fullUrl,
|
|
212
268
|
type: input.type,
|
|
213
269
|
request: input.request,
|
|
214
270
|
});
|
|
215
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Consent-first helper for developer onboarding.
|
|
274
|
+
*
|
|
275
|
+
* Expected keys should come from `ClaimConsent` in caller code.
|
|
276
|
+
*/
|
|
277
|
+
upsertActiveConsentEntry(input) {
|
|
278
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Consent, input);
|
|
279
|
+
}
|
|
216
280
|
/**
|
|
217
281
|
* MedicationStatement helper for IPS-in-Communication use cases.
|
|
218
282
|
*
|
|
219
283
|
* Expected keys should come from MedicationStatement claims constants.
|
|
220
284
|
*/
|
|
221
285
|
upsertActiveMedicationStatementEntry(input) {
|
|
222
|
-
return this.
|
|
223
|
-
resourceType: ResourceTypesFhirR4.MedicationStatement,
|
|
224
|
-
claims: {
|
|
225
|
-
...input.claims,
|
|
226
|
-
},
|
|
227
|
-
fullUrl: input.fullUrl,
|
|
228
|
-
type: input.type,
|
|
229
|
-
request: input.request,
|
|
230
|
-
});
|
|
286
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.MedicationStatement, input);
|
|
231
287
|
}
|
|
232
288
|
/**
|
|
233
289
|
* DocumentReference helper for bundle-contained attachments linked from
|
|
234
|
-
* other clinical resources through `*.contained-
|
|
290
|
+
* other clinical resources through `*.contained-reference-list`
|
|
291
|
+
* (with `*.contained-documents` kept as legacy compatibility alias).
|
|
235
292
|
*/
|
|
236
293
|
upsertActiveDocumentReferenceEntry(input) {
|
|
237
|
-
return this.
|
|
238
|
-
resourceType: ResourceTypesFhirR4.DocumentReference,
|
|
239
|
-
claims: {
|
|
240
|
-
...input.claims,
|
|
241
|
-
},
|
|
242
|
-
fullUrl: input.fullUrl,
|
|
243
|
-
type: input.type,
|
|
244
|
-
request: input.request,
|
|
245
|
-
});
|
|
294
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.DocumentReference, input);
|
|
246
295
|
}
|
|
247
296
|
/**
|
|
248
297
|
* Condition helper for IPS-in-Communication use cases.
|
|
@@ -250,15 +299,15 @@ export class CommunicationAttachedBundleSession {
|
|
|
250
299
|
* Expected keys should come from Condition claims constants.
|
|
251
300
|
*/
|
|
252
301
|
upsertActiveConditionEntry(input) {
|
|
253
|
-
return this.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
302
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Condition, input);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Appointment helper for schedule/event flows carried inside Communication.
|
|
306
|
+
*
|
|
307
|
+
* Expected keys should come from Appointment claims constants.
|
|
308
|
+
*/
|
|
309
|
+
upsertActiveAppointmentEntry(input) {
|
|
310
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Appointment, input);
|
|
262
311
|
}
|
|
263
312
|
/**
|
|
264
313
|
* Observation helper for IPS-style and sectioned bundle authoring.
|
|
@@ -266,15 +315,7 @@ export class CommunicationAttachedBundleSession {
|
|
|
266
315
|
* Expected keys should come from Observation claims constants.
|
|
267
316
|
*/
|
|
268
317
|
upsertActiveObservationEntry(input) {
|
|
269
|
-
return this.
|
|
270
|
-
resourceType: ResourceTypesFhirR4.Observation,
|
|
271
|
-
claims: {
|
|
272
|
-
...input.claims,
|
|
273
|
-
},
|
|
274
|
-
fullUrl: input.fullUrl,
|
|
275
|
-
type: input.type,
|
|
276
|
-
request: input.request,
|
|
277
|
-
});
|
|
318
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Observation, input);
|
|
278
319
|
}
|
|
279
320
|
/**
|
|
280
321
|
* AllergyIntolerance helper for IPS-in-Communication use cases.
|
|
@@ -282,36 +323,72 @@ export class CommunicationAttachedBundleSession {
|
|
|
282
323
|
* Expected keys should come from AllergyIntolerance claims constants.
|
|
283
324
|
*/
|
|
284
325
|
upsertActiveAllergyIntoleranceEntry(input) {
|
|
285
|
-
return this.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
326
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.AllergyIntolerance, input);
|
|
327
|
+
}
|
|
328
|
+
/** DiagnosticReport helper for report-in-Communication use cases. */
|
|
329
|
+
upsertActiveDiagnosticReportEntry(input) {
|
|
330
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.DiagnosticReport, input);
|
|
331
|
+
}
|
|
332
|
+
/** CarePlan helper for plan-in-Communication use cases. */
|
|
333
|
+
upsertActiveCarePlanEntry(input) {
|
|
334
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.CarePlan, input);
|
|
335
|
+
}
|
|
336
|
+
/** Procedure helper for procedure-in-Communication use cases. */
|
|
337
|
+
upsertActiveProcedureEntry(input) {
|
|
338
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Procedure, input);
|
|
339
|
+
}
|
|
340
|
+
/** Immunization helper for immunization-in-Communication use cases. */
|
|
341
|
+
upsertActiveImmunizationEntry(input) {
|
|
342
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Immunization, input);
|
|
343
|
+
}
|
|
344
|
+
/** Encounter helper for visit/event flows carried inside Communication. */
|
|
345
|
+
upsertActiveEncounterEntry(input) {
|
|
346
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Encounter, input);
|
|
347
|
+
}
|
|
348
|
+
/** Device helper for device catalog or implant/device context flows. */
|
|
349
|
+
upsertActiveDeviceEntry(input) {
|
|
350
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Device, input);
|
|
351
|
+
}
|
|
352
|
+
/** DeviceUseStatement helper for device usage flows carried inside Communication. */
|
|
353
|
+
upsertActiveDeviceUseStatementEntry(input) {
|
|
354
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.DeviceUseStatement, input);
|
|
355
|
+
}
|
|
356
|
+
/** Flag helper for alert/banner flows carried inside Communication. */
|
|
357
|
+
upsertActiveFlagEntry(input) {
|
|
358
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Flag, input);
|
|
359
|
+
}
|
|
360
|
+
/** ClinicalImpression helper for assessment flows carried inside Communication. */
|
|
361
|
+
upsertActiveClinicalImpressionEntry(input) {
|
|
362
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.ClinicalImpression, input);
|
|
363
|
+
}
|
|
364
|
+
/** Coverage helper for entitlement/insurance flows carried inside Communication. */
|
|
365
|
+
upsertActiveCoverageEntry(input) {
|
|
366
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Coverage, input);
|
|
367
|
+
}
|
|
368
|
+
/** AppointmentResponse helper for schedule-response flows carried inside Communication. */
|
|
369
|
+
upsertActiveAppointmentResponseEntry(input) {
|
|
370
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.AppointmentResponse, input);
|
|
371
|
+
}
|
|
372
|
+
/** Composition helper for document-structure flows carried inside Communication. */
|
|
373
|
+
upsertActiveCompositionEntry(input) {
|
|
374
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Composition, input);
|
|
375
|
+
}
|
|
376
|
+
/** Location helper for schedule/place flows carried inside Communication. */
|
|
377
|
+
upsertActiveLocationEntry(input) {
|
|
378
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Location, input);
|
|
379
|
+
}
|
|
380
|
+
/** Organization helper for provider/payer/department flows carried inside Communication. */
|
|
381
|
+
upsertActiveOrganizationEntry(input) {
|
|
382
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.Organization, input);
|
|
383
|
+
}
|
|
384
|
+
/** RelatedPerson helper for relationship/member flows carried inside Communication. */
|
|
385
|
+
upsertActiveRelatedPersonEntry(input) {
|
|
386
|
+
return this.upsertActiveClaimsResourceEntry(ResourceTypesFhirR4.RelatedPerson, input);
|
|
294
387
|
}
|
|
295
|
-
/**
|
|
296
|
-
* TODO(ips-next):
|
|
297
|
-
* Add `upsertActiveDiagnosticReportEntry(...)` once the shared claim helpers
|
|
298
|
-
* for `DiagnosticReport` are in place.
|
|
299
|
-
*
|
|
300
|
-
* Expected shape should mirror the existing resource helpers:
|
|
301
|
-
* - `claims` authored with `@context = org.hl7.fhir.api`
|
|
302
|
-
* - matching priority by `DiagnosticReport.identifier`
|
|
303
|
-
* - support for linked `DocumentReference` ids through
|
|
304
|
-
* `DiagnosticReport.contained-documents`
|
|
305
|
-
*
|
|
306
|
-
* Intentionally not implemented in this pass:
|
|
307
|
-
* - IPS authoring already works for the currently documented resources
|
|
308
|
-
* - GW Core can already consume bundle-contained `DocumentReference` rows
|
|
309
|
-
* - adding the DiagnosticReport editing surface now would expand the IPS
|
|
310
|
-
* contract further than intended for this release slice
|
|
311
|
-
*/
|
|
312
388
|
/**
|
|
313
389
|
* Creates or updates a linked `DocumentReference` entry and stores its
|
|
314
|
-
* identifier under the active resource
|
|
390
|
+
* identifier/reference under the active resource
|
|
391
|
+
* `*.contained-reference-list` claim.
|
|
315
392
|
*/
|
|
316
393
|
addContainedDocumentToActiveEntry(input) {
|
|
317
394
|
if (this.activeEntryIndex === null) {
|
|
@@ -335,7 +412,7 @@ export class CommunicationAttachedBundleSession {
|
|
|
335
412
|
|| resolveSubjectFromClaims(parentClaims)
|
|
336
413
|
|| asTrimmedString(this.communicationClaims[CommunicationClaim.Subject]);
|
|
337
414
|
const documentClaims = {
|
|
338
|
-
'@context':
|
|
415
|
+
'@context': BundleEntryClaimsContext,
|
|
339
416
|
...(input.claims || {}),
|
|
340
417
|
[DocumentReferenceClaim.Identifier]: documentIdentifier,
|
|
341
418
|
};
|
|
@@ -460,29 +537,9 @@ export class CommunicationAttachedBundleSession {
|
|
|
460
537
|
resolveCurrentSubject() {
|
|
461
538
|
if (this.activeEntryIndex !== null) {
|
|
462
539
|
const claims = this.bundleInMemory.data[this.activeEntryIndex]?.resource?.meta?.claims || {};
|
|
463
|
-
const
|
|
464
|
-
if (
|
|
465
|
-
return
|
|
466
|
-
}
|
|
467
|
-
const medicationSubject = asTrimmedString(claims[MedicationStatementClaim.Subject]);
|
|
468
|
-
if (medicationSubject) {
|
|
469
|
-
return medicationSubject;
|
|
470
|
-
}
|
|
471
|
-
const conditionSubject = asTrimmedString(claims[ConditionClaim.Subject]);
|
|
472
|
-
if (conditionSubject) {
|
|
473
|
-
return conditionSubject;
|
|
474
|
-
}
|
|
475
|
-
const allergySubject = asTrimmedString(claims[AllergyIntoleranceClaim.Subject] || claims[AllergyIntoleranceClaim.Patient]);
|
|
476
|
-
if (allergySubject) {
|
|
477
|
-
return allergySubject;
|
|
478
|
-
}
|
|
479
|
-
const documentReferenceSubject = asTrimmedString(claims[DocumentReferenceClaim.Subject]);
|
|
480
|
-
if (documentReferenceSubject) {
|
|
481
|
-
return documentReferenceSubject;
|
|
482
|
-
}
|
|
483
|
-
const observationSubject = asTrimmedString(claims[ObservationClaim.Subject] || claims[ObservationClaim.Patient]);
|
|
484
|
-
if (observationSubject) {
|
|
485
|
-
return observationSubject;
|
|
540
|
+
const entrySubject = resolveSubjectFromClaims(claims);
|
|
541
|
+
if (entrySubject) {
|
|
542
|
+
return entrySubject;
|
|
486
543
|
}
|
|
487
544
|
}
|
|
488
545
|
const fromClaims = asTrimmedString(this.communicationClaims[CommunicationClaim.Subject]);
|
|
@@ -506,31 +563,7 @@ export class CommunicationAttachedBundleSession {
|
|
|
506
563
|
});
|
|
507
564
|
}
|
|
508
565
|
resolveEntryIdentifier(claims) {
|
|
509
|
-
|
|
510
|
-
if (consentIdentifier) {
|
|
511
|
-
return `${ResourceTypesFhirR4.Consent}:${consentIdentifier}`;
|
|
512
|
-
}
|
|
513
|
-
const medicationIdentifier = asTrimmedString(claims[MedicationStatementClaim.Identifier]);
|
|
514
|
-
if (medicationIdentifier) {
|
|
515
|
-
return `${ResourceTypesFhirR4.MedicationStatement}:${medicationIdentifier}`;
|
|
516
|
-
}
|
|
517
|
-
const conditionIdentifier = asTrimmedString(claims[ConditionClaim.Identifier]);
|
|
518
|
-
if (conditionIdentifier) {
|
|
519
|
-
return `${ResourceTypesFhirR4.Condition}:${conditionIdentifier}`;
|
|
520
|
-
}
|
|
521
|
-
const allergyIdentifier = asTrimmedString(claims[AllergyIntoleranceClaim.Identifier]);
|
|
522
|
-
if (allergyIdentifier) {
|
|
523
|
-
return `${ResourceTypesFhirR4.AllergyIntolerance}:${allergyIdentifier}`;
|
|
524
|
-
}
|
|
525
|
-
const documentReferenceIdentifier = asTrimmedString(claims[DocumentReferenceClaim.Identifier]);
|
|
526
|
-
if (documentReferenceIdentifier) {
|
|
527
|
-
return `${ResourceTypesFhirR4.DocumentReference}:${documentReferenceIdentifier}`;
|
|
528
|
-
}
|
|
529
|
-
const observationIdentifier = asTrimmedString(claims[ObservationClaim.Identifier]);
|
|
530
|
-
if (observationIdentifier) {
|
|
531
|
-
return `${ResourceTypesFhirR4.Observation}:${observationIdentifier}`;
|
|
532
|
-
}
|
|
533
|
-
return '';
|
|
566
|
+
return resolveBundleEntryIdentifier(claims);
|
|
534
567
|
}
|
|
535
568
|
createBundleEntry(input) {
|
|
536
569
|
const resourceClaims = {
|
|
@@ -551,35 +584,7 @@ export class CommunicationAttachedBundleSession {
|
|
|
551
584
|
};
|
|
552
585
|
}
|
|
553
586
|
resolveEntryCanonicalIdValue(claims) {
|
|
554
|
-
|
|
555
|
-
if (consentIdentifier) {
|
|
556
|
-
return consentIdentifier;
|
|
557
|
-
}
|
|
558
|
-
const medicationIdentifier = asTrimmedString(claims[MedicationStatementClaim.Identifier]);
|
|
559
|
-
if (medicationIdentifier) {
|
|
560
|
-
return medicationIdentifier;
|
|
561
|
-
}
|
|
562
|
-
const conditionIdentifier = asTrimmedString(claims[ConditionClaim.Identifier]);
|
|
563
|
-
if (conditionIdentifier) {
|
|
564
|
-
return conditionIdentifier;
|
|
565
|
-
}
|
|
566
|
-
const allergyIdentifier = asTrimmedString(claims[AllergyIntoleranceClaim.Identifier]);
|
|
567
|
-
if (allergyIdentifier) {
|
|
568
|
-
return allergyIdentifier;
|
|
569
|
-
}
|
|
570
|
-
const documentReferenceIdentifier = asTrimmedString(claims[DocumentReferenceClaim.Identifier]);
|
|
571
|
-
if (documentReferenceIdentifier) {
|
|
572
|
-
return documentReferenceIdentifier;
|
|
573
|
-
}
|
|
574
|
-
const observationIdentifier = asTrimmedString(claims[ObservationClaim.Identifier]);
|
|
575
|
-
if (observationIdentifier) {
|
|
576
|
-
return observationIdentifier;
|
|
577
|
-
}
|
|
578
|
-
const communicationIdentifier = asTrimmedString(claims[CommunicationClaim.Identifier]);
|
|
579
|
-
if (communicationIdentifier) {
|
|
580
|
-
return communicationIdentifier;
|
|
581
|
-
}
|
|
582
|
-
return '';
|
|
587
|
+
return resolveBundleEntryCanonicalIdValue(claims);
|
|
583
588
|
}
|
|
584
589
|
assertEntryIndex(index) {
|
|
585
590
|
if (!Number.isInteger(index) || index < 0 || index >= this.bundleInMemory.data.length) {
|
|
@@ -587,674 +592,3 @@ export class CommunicationAttachedBundleSession {
|
|
|
587
592
|
}
|
|
588
593
|
}
|
|
589
594
|
}
|
|
590
|
-
/**
|
|
591
|
-
* High-level consent-access editor alias for onboarding and app-facing code.
|
|
592
|
-
*
|
|
593
|
-
* This keeps the business intent explicit for developers who are editing
|
|
594
|
-
* Consent access rules inside a Communication-carried bundle and should not
|
|
595
|
-
* need to start from the lower-level generic session name.
|
|
596
|
-
*/
|
|
597
|
-
export class ConsentAccessEditor extends CommunicationAttachedBundleSession {
|
|
598
|
-
/** Returns duplicate atomic consent-rule conflicts across the current bundle. */
|
|
599
|
-
getConsentRuleDuplicateConflicts() {
|
|
600
|
-
return detectDuplicateConsentRuleConflicts(this.getBundleInMemory().data);
|
|
601
|
-
}
|
|
602
|
-
/** Returns duplicate atomic consent-rule conflicts affecting the active Consent entry. */
|
|
603
|
-
getActiveConsentRuleDuplicateConflicts() {
|
|
604
|
-
const activeEntryIndex = this.getActiveEntryIndex();
|
|
605
|
-
if (activeEntryIndex === null) {
|
|
606
|
-
return [];
|
|
607
|
-
}
|
|
608
|
-
return this.getConsentRuleDuplicateConflicts()
|
|
609
|
-
.filter((conflict) => conflict.affectedEntries.some((entry) => entry.entryIndex === activeEntryIndex));
|
|
610
|
-
}
|
|
611
|
-
/** Returns one frontend-facing editable view model for the active Consent entry. */
|
|
612
|
-
getConsentViewModel() {
|
|
613
|
-
const activeEntry = this.getActiveEntry();
|
|
614
|
-
const claims = {
|
|
615
|
-
...(activeEntry?.resource?.meta?.claims || {}),
|
|
616
|
-
};
|
|
617
|
-
return {
|
|
618
|
-
...(activeEntry?.fullUrl ? { fullUrl: activeEntry.fullUrl } : {}),
|
|
619
|
-
identifier: asTrimmedString(claims[ClaimConsent.identifier]),
|
|
620
|
-
subject: asTrimmedString(claims[ClaimConsent.subject]),
|
|
621
|
-
decision: this.getDecision(),
|
|
622
|
-
classifiedActors: this.getActorsClassified(),
|
|
623
|
-
classifiedRoles: this.getRolesClassified(),
|
|
624
|
-
classifiedPurposes: this.getPurposesClassified(),
|
|
625
|
-
classifiedTargets: this.getTargetsClassified(),
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
/** Applies one frontend-facing editable view model back into the active Consent entry. */
|
|
629
|
-
applyConsentViewModel(viewModel) {
|
|
630
|
-
this.setActiveEntryClaim(ClaimConsent.identifier, viewModel.identifier);
|
|
631
|
-
this.setActiveEntryClaim(ClaimConsent.subject, viewModel.subject);
|
|
632
|
-
this.setActiveEntryClaim(ClaimConsent.decision, viewModel.decision);
|
|
633
|
-
this.setActiveEntryClaimList(ClaimConsent.actorIdentifier, flattenClassifiedActors(viewModel.classifiedActors));
|
|
634
|
-
this.setSelectedRoles(flattenClassifiedRoles(viewModel.classifiedRoles));
|
|
635
|
-
this.setSelectedPurposes(viewModel.classifiedPurposes.map((purpose) => purpose.code));
|
|
636
|
-
const flattenedTargets = flattenClassifiedTargets(viewModel.classifiedTargets);
|
|
637
|
-
this.setSelectedCoreSections(flattenedTargets.coreSections);
|
|
638
|
-
this.setSelectedKindOfDocuments(flattenedTargets.kindOfDocuments);
|
|
639
|
-
this.setSelectedTypeOfServices(flattenedTargets.typeOfServices);
|
|
640
|
-
this.setSelectedSubjectMatterDomains(flattenedTargets.subjectMatterDomains);
|
|
641
|
-
this.setSelectedResourceTypes(flattenedTargets.resourceTypes);
|
|
642
|
-
return this;
|
|
643
|
-
}
|
|
644
|
-
/** Returns the canonical permit/deny decision from the active Consent entry. */
|
|
645
|
-
getDecision() {
|
|
646
|
-
return asTrimmedString(this.getActiveEntryClaim(ClaimConsent.decision));
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* @deprecated Use `getDecision()`.
|
|
650
|
-
* Kept as a compatibility alias for the previous helper name.
|
|
651
|
-
*/
|
|
652
|
-
getPermit() {
|
|
653
|
-
return this.getDecision();
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* Returns target classification derived from the current consent claim
|
|
657
|
-
* contract without altering or extending the persisted claim keys.
|
|
658
|
-
*/
|
|
659
|
-
getTargetsClassified() {
|
|
660
|
-
const claims = {
|
|
661
|
-
...(this.getActiveEntry()?.resource?.meta?.claims || {}),
|
|
662
|
-
};
|
|
663
|
-
const actionTargets = splitCsv(claims[ClaimConsent.action]).map((code) => buildClassifiedConsentTarget(ConsentEditorTargetKinds.Section, code, [ConsentEditorScopeCodes.Read]));
|
|
664
|
-
const categoryTargets = splitCsv(claims[ClaimConsent.category]).map((code) => buildClassifiedConsentTarget(ConsentEditorTargetKinds.Section, code, [ConsentEditorScopeCodes.Read], HealthcareCanonicalSectionFamilies.KindOfDocument));
|
|
665
|
-
const resourceTypes = splitCsv(claims[ClaimConsent.resourceType]).map((code) => buildClassifiedConsentTarget(ConsentEditorTargetKinds.ResourceType, code, [ConsentEditorScopeCodes.Read]));
|
|
666
|
-
return normalizeClassifiedTargets([
|
|
667
|
-
...actionTargets,
|
|
668
|
-
...categoryTargets,
|
|
669
|
-
...resourceTypes,
|
|
670
|
-
]);
|
|
671
|
-
}
|
|
672
|
-
/** Returns the catalog of core-section targets available for picker UIs. */
|
|
673
|
-
getCoreSectionOptions() {
|
|
674
|
-
return buildSectionCatalogOptions(HealthcareCanonicalSectionFamilies.CoreSection);
|
|
675
|
-
}
|
|
676
|
-
/** Returns the core-section catalog keyed by LOINC code. */
|
|
677
|
-
getCoreSectionCatalog() {
|
|
678
|
-
return getHealthcareSectionsByFamily(HealthcareCanonicalSectionFamilies.CoreSection);
|
|
679
|
-
}
|
|
680
|
-
/** Returns the catalog of kind-of-document targets available for picker UIs. */
|
|
681
|
-
getKindOfDocumentOptions() {
|
|
682
|
-
return buildSectionCatalogOptions(HealthcareCanonicalSectionFamilies.KindOfDocument);
|
|
683
|
-
}
|
|
684
|
-
/** Returns the kind-of-document catalog keyed by LOINC code. */
|
|
685
|
-
getKindOfDocumentCatalog() {
|
|
686
|
-
return getHealthcareSectionsByFamily(HealthcareCanonicalSectionFamilies.KindOfDocument);
|
|
687
|
-
}
|
|
688
|
-
/** Returns the catalog of type-of-service targets available for picker UIs. */
|
|
689
|
-
getTypeOfServiceOptions() {
|
|
690
|
-
return buildSectionCatalogOptions(HealthcareCanonicalSectionFamilies.TypeOfService);
|
|
691
|
-
}
|
|
692
|
-
/** Returns the type-of-service catalog keyed by LOINC code. */
|
|
693
|
-
getTypeOfServiceCatalog() {
|
|
694
|
-
return getHealthcareSectionsByFamily(HealthcareCanonicalSectionFamilies.TypeOfService);
|
|
695
|
-
}
|
|
696
|
-
/** Returns the catalog of subject-matter-domain targets available for picker UIs. */
|
|
697
|
-
getSubjectMatterDomainOptions() {
|
|
698
|
-
return buildSectionCatalogOptions(HealthcareCanonicalSectionFamilies.SubjectMatterDomain);
|
|
699
|
-
}
|
|
700
|
-
/** Returns the subject-matter-domain catalog keyed by LOINC code. */
|
|
701
|
-
getSubjectMatterDomainCatalog() {
|
|
702
|
-
return getHealthcareSectionsByFamily(HealthcareCanonicalSectionFamilies.SubjectMatterDomain);
|
|
703
|
-
}
|
|
704
|
-
/** Returns resource-type targets currently known in the active consent entry. */
|
|
705
|
-
getResourceTypeOptions() {
|
|
706
|
-
return this.getSelectedResourceTypes().map((code) => buildClassifiedConsentTarget(ConsentEditorTargetKinds.ResourceType, code, [ConsentEditorScopeCodes.Read]));
|
|
707
|
-
}
|
|
708
|
-
/** Returns the supported resource-type catalog. */
|
|
709
|
-
getResourceTypeCatalog() {
|
|
710
|
-
return Object.values(ResourceTypesFhirR4).filter((resourceType) => resourceType !== ResourceTypesFhirR4.Bundle);
|
|
711
|
-
}
|
|
712
|
-
/** Returns the available professional roles for one sector. */
|
|
713
|
-
getAvailableProfessionalRolesBySector(sector) {
|
|
714
|
-
return getHealthcareProfessionalRolesBySector(sector);
|
|
715
|
-
}
|
|
716
|
-
/** Returns the available HL7 relationship roles. */
|
|
717
|
-
getAvailableRelationshipRoles() {
|
|
718
|
-
return getHealthcareRolesByFamily(HealthcareRoleFamilies.PersonalRelationshipHl7);
|
|
719
|
-
}
|
|
720
|
-
/** Returns selected core-section claim values. */
|
|
721
|
-
getSelectedCoreSections() {
|
|
722
|
-
return this.getSelectedSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.CoreSection);
|
|
723
|
-
}
|
|
724
|
-
/** Returns selected kind-of-document claim values. */
|
|
725
|
-
getSelectedKindOfDocuments() {
|
|
726
|
-
return splitCsv(this.getActiveEntryClaim(ClaimConsent.category));
|
|
727
|
-
}
|
|
728
|
-
/** Returns selected type-of-service claim values. */
|
|
729
|
-
getSelectedTypeOfServices() {
|
|
730
|
-
return this.getSelectedSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.TypeOfService);
|
|
731
|
-
}
|
|
732
|
-
/** Returns selected subject-matter-domain claim values. */
|
|
733
|
-
getSelectedSubjectMatterDomains() {
|
|
734
|
-
return this.getSelectedSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.SubjectMatterDomain);
|
|
735
|
-
}
|
|
736
|
-
/** Returns selected resource-type claim values. */
|
|
737
|
-
getSelectedResourceTypes() {
|
|
738
|
-
return splitCsv(this.getActiveEntryClaim(ClaimConsent.resourceType));
|
|
739
|
-
}
|
|
740
|
-
/** Replaces selected core-section claim values while preserving other families. */
|
|
741
|
-
setSelectedCoreSections(codes) {
|
|
742
|
-
return this.setSelectedSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.CoreSection, codes);
|
|
743
|
-
}
|
|
744
|
-
/** Replaces selected kind-of-document claim values. */
|
|
745
|
-
setSelectedKindOfDocuments(codes) {
|
|
746
|
-
return this.setActiveEntryClaimList(ClaimConsent.category, codes);
|
|
747
|
-
}
|
|
748
|
-
/** Replaces selected type-of-service claim values while preserving other families. */
|
|
749
|
-
setSelectedTypeOfServices(codes) {
|
|
750
|
-
return this.setSelectedSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.TypeOfService, codes);
|
|
751
|
-
}
|
|
752
|
-
/** Replaces selected subject-matter-domain claim values while preserving other families. */
|
|
753
|
-
setSelectedSubjectMatterDomains(codes) {
|
|
754
|
-
return this.setSelectedSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.SubjectMatterDomain, codes);
|
|
755
|
-
}
|
|
756
|
-
/** Replaces selected resource-type claim values. */
|
|
757
|
-
setSelectedResourceTypes(codes) {
|
|
758
|
-
return this.setActiveEntryClaimList(ClaimConsent.resourceType, codes);
|
|
759
|
-
}
|
|
760
|
-
/** Adds core-section claim values without removing other selected targets. */
|
|
761
|
-
addCoreSections(codes) {
|
|
762
|
-
return this.addSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.CoreSection, codes);
|
|
763
|
-
}
|
|
764
|
-
/** Removes selected core-section claim values. */
|
|
765
|
-
removeCoreSections(codes) {
|
|
766
|
-
return this.removeSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.CoreSection, codes);
|
|
767
|
-
}
|
|
768
|
-
/** Adds kind-of-document claim values. */
|
|
769
|
-
addKindOfDocuments(codes) {
|
|
770
|
-
return this.addActiveEntryClaimList(ClaimConsent.category, codes);
|
|
771
|
-
}
|
|
772
|
-
/** Removes kind-of-document claim values. */
|
|
773
|
-
removeKindOfDocuments(codes) {
|
|
774
|
-
return this.removeActiveEntryClaimList(ClaimConsent.category, codes);
|
|
775
|
-
}
|
|
776
|
-
/** Adds type-of-service claim values without removing other selected targets. */
|
|
777
|
-
addTypeOfServices(codes) {
|
|
778
|
-
return this.addSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.TypeOfService, codes);
|
|
779
|
-
}
|
|
780
|
-
/** Removes selected type-of-service claim values. */
|
|
781
|
-
removeTypeOfServices(codes) {
|
|
782
|
-
return this.removeSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.TypeOfService, codes);
|
|
783
|
-
}
|
|
784
|
-
/** Adds subject-matter-domain claim values without removing other selected targets. */
|
|
785
|
-
addSubjectMatterDomains(codes) {
|
|
786
|
-
return this.addSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.SubjectMatterDomain, codes);
|
|
787
|
-
}
|
|
788
|
-
/** Removes selected subject-matter-domain claim values. */
|
|
789
|
-
removeSubjectMatterDomains(codes) {
|
|
790
|
-
return this.removeSectionCodesByFamily(ClaimConsent.action, HealthcareCanonicalSectionFamilies.SubjectMatterDomain, codes);
|
|
791
|
-
}
|
|
792
|
-
/** Adds resource-type claim values. */
|
|
793
|
-
addResourceTypes(codes) {
|
|
794
|
-
return this.addActiveEntryClaimList(ClaimConsent.resourceType, codes);
|
|
795
|
-
}
|
|
796
|
-
/** Removes resource-type claim values. */
|
|
797
|
-
removeResourceTypes(codes) {
|
|
798
|
-
return this.removeActiveEntryClaimList(ClaimConsent.resourceType, codes);
|
|
799
|
-
}
|
|
800
|
-
/** Returns purposes as explicit classified items. */
|
|
801
|
-
getPurposesClassified() {
|
|
802
|
-
return this.getSelectedPurposes().map((code) => ({
|
|
803
|
-
code,
|
|
804
|
-
display: resolveConsentPurposeDisplay(code),
|
|
805
|
-
}));
|
|
806
|
-
}
|
|
807
|
-
/** Returns selected purpose claim values. */
|
|
808
|
-
getSelectedPurposes() {
|
|
809
|
-
return splitCsv(this.getActiveEntryClaim(ClaimConsent.purpose));
|
|
810
|
-
}
|
|
811
|
-
/** Replaces selected purpose claim values. */
|
|
812
|
-
setSelectedPurposes(codes) {
|
|
813
|
-
return this.setActiveEntryClaimList(ClaimConsent.purpose, codes);
|
|
814
|
-
}
|
|
815
|
-
/** Adds purpose claim values. */
|
|
816
|
-
addPurposes(codes) {
|
|
817
|
-
return this.addActiveEntryClaimList(ClaimConsent.purpose, codes);
|
|
818
|
-
}
|
|
819
|
-
/** Removes purpose claim values. */
|
|
820
|
-
removePurposes(codes) {
|
|
821
|
-
return this.removeActiveEntryClaimList(ClaimConsent.purpose, codes);
|
|
822
|
-
}
|
|
823
|
-
/** Returns selected actor roles as explicit classified items. */
|
|
824
|
-
getRolesClassified() {
|
|
825
|
-
const classified = {
|
|
826
|
-
professional: [],
|
|
827
|
-
relationship: [],
|
|
828
|
-
legalRepresentative: [],
|
|
829
|
-
other: [],
|
|
830
|
-
};
|
|
831
|
-
for (const roleToken of this.getSelectedRoles()) {
|
|
832
|
-
const role = getHealthcareRoleByClaim(roleToken);
|
|
833
|
-
if (!role) {
|
|
834
|
-
classified.other.push({
|
|
835
|
-
codingSystem: '',
|
|
836
|
-
code: roleToken,
|
|
837
|
-
display: roleToken,
|
|
838
|
-
});
|
|
839
|
-
continue;
|
|
840
|
-
}
|
|
841
|
-
const nextRole = {
|
|
842
|
-
family: role.family,
|
|
843
|
-
codingSystem: role.codingSystem,
|
|
844
|
-
code: role.code,
|
|
845
|
-
display: role.titleEn,
|
|
846
|
-
definition: role.definition,
|
|
847
|
-
};
|
|
848
|
-
if (role.family === HealthcareRoleFamilies.ProfessionalOccupationIsco08) {
|
|
849
|
-
classified.professional.push(nextRole);
|
|
850
|
-
}
|
|
851
|
-
else if (role.family === HealthcareRoleFamilies.PersonalRelationshipHl7) {
|
|
852
|
-
classified.relationship.push(nextRole);
|
|
853
|
-
}
|
|
854
|
-
else if (role.family === HealthcareRoleFamilies.LegalRepresentativeHl7) {
|
|
855
|
-
classified.legalRepresentative.push(nextRole);
|
|
856
|
-
}
|
|
857
|
-
else {
|
|
858
|
-
classified.other.push(nextRole);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
return classified;
|
|
862
|
-
}
|
|
863
|
-
/** Returns selected actor-role claim values. */
|
|
864
|
-
getSelectedRoles() {
|
|
865
|
-
return splitCsv(this.getActiveEntryClaim(ClaimConsent.actorRole));
|
|
866
|
-
}
|
|
867
|
-
/** Replaces selected actor-role claim values. */
|
|
868
|
-
setSelectedRoles(codes) {
|
|
869
|
-
return this.setActiveEntryClaimList(ClaimConsent.actorRole, codes);
|
|
870
|
-
}
|
|
871
|
-
/** Adds actor-role claim values. */
|
|
872
|
-
addRoles(codes) {
|
|
873
|
-
return this.addActiveEntryClaimList(ClaimConsent.actorRole, codes);
|
|
874
|
-
}
|
|
875
|
-
/** Removes actor-role claim values. */
|
|
876
|
-
removeRoles(codes) {
|
|
877
|
-
return this.removeActiveEntryClaimList(ClaimConsent.actorRole, codes);
|
|
878
|
-
}
|
|
879
|
-
/** Returns consent actors grouped by jurisdiction, organization, and user. */
|
|
880
|
-
getActorsClassified() {
|
|
881
|
-
const claims = {
|
|
882
|
-
...(this.getActiveEntry()?.resource?.meta?.claims || {}),
|
|
883
|
-
};
|
|
884
|
-
const actorTokens = splitCsv(claims[ClaimConsent.actorIdentifier]);
|
|
885
|
-
const actorRole = parseConsentActorRole(splitCsv(claims[ClaimConsent.actorRole])[0] || '');
|
|
886
|
-
const jurisdictions = new Map();
|
|
887
|
-
const organizations = new Map();
|
|
888
|
-
const users = new Map();
|
|
889
|
-
for (const token of actorTokens) {
|
|
890
|
-
if (looksLikeJurisdictionToken(token)) {
|
|
891
|
-
jurisdictions.set(token, { code: token, display: token });
|
|
892
|
-
continue;
|
|
893
|
-
}
|
|
894
|
-
if (looksLikeEmailToken(token)) {
|
|
895
|
-
users.set(`email:${token}`, {
|
|
896
|
-
email: token,
|
|
897
|
-
...(actorRole ? { role: actorRole } : {}),
|
|
898
|
-
});
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
if (looksLikePhoneToken(token)) {
|
|
902
|
-
users.set(`phone:${token}`, {
|
|
903
|
-
phone: token,
|
|
904
|
-
...(actorRole ? { role: actorRole } : {}),
|
|
905
|
-
});
|
|
906
|
-
continue;
|
|
907
|
-
}
|
|
908
|
-
const organization = parseDidWebOrganizationToken(token);
|
|
909
|
-
if (organization) {
|
|
910
|
-
organizations.set(organization.domain, organization);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
return {
|
|
914
|
-
jurisdictions: Array.from(jurisdictions.values()),
|
|
915
|
-
organizations: Array.from(organizations.values()),
|
|
916
|
-
users: Array.from(users.values()),
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
getSelectedSectionCodesByFamily(claimKey, family) {
|
|
920
|
-
return splitCsv(this.getActiveEntryClaim(claimKey))
|
|
921
|
-
.filter((code) => getHealthcareSectionFamilyByCode(code) === family);
|
|
922
|
-
}
|
|
923
|
-
setSelectedSectionCodesByFamily(claimKey, family, codes) {
|
|
924
|
-
const preserved = splitCsv(this.getActiveEntryClaim(claimKey))
|
|
925
|
-
.filter((code) => getHealthcareSectionFamilyByCode(code) !== family);
|
|
926
|
-
return this.setActiveEntryClaimList(claimKey, [...preserved, ...codes]);
|
|
927
|
-
}
|
|
928
|
-
addSectionCodesByFamily(claimKey, family, codes) {
|
|
929
|
-
const next = [...this.getSelectedSectionCodesByFamily(claimKey, family), ...codes];
|
|
930
|
-
return this.setSelectedSectionCodesByFamily(claimKey, family, next);
|
|
931
|
-
}
|
|
932
|
-
removeSectionCodesByFamily(claimKey, family, codes) {
|
|
933
|
-
const codesToRemove = new Set(normalizeCsvValues(codes));
|
|
934
|
-
const next = this.getSelectedSectionCodesByFamily(claimKey, family)
|
|
935
|
-
.filter((code) => !codesToRemove.has(code));
|
|
936
|
-
return this.setSelectedSectionCodesByFamily(claimKey, family, next);
|
|
937
|
-
}
|
|
938
|
-
setActiveEntryClaimList(key, values) {
|
|
939
|
-
const normalized = normalizeCsvValues(values);
|
|
940
|
-
if (normalized.length === 0) {
|
|
941
|
-
return this.removeActiveEntryClaim(key);
|
|
942
|
-
}
|
|
943
|
-
return this.setActiveEntryClaim(key, normalized.join(CSV_SEPARATOR));
|
|
944
|
-
}
|
|
945
|
-
addActiveEntryClaimList(key, values) {
|
|
946
|
-
const next = normalizeCsvValues([
|
|
947
|
-
...splitCsv(this.getActiveEntryClaim(key)),
|
|
948
|
-
...values,
|
|
949
|
-
]);
|
|
950
|
-
return this.setActiveEntryClaimList(key, next);
|
|
951
|
-
}
|
|
952
|
-
removeActiveEntryClaimList(key, values) {
|
|
953
|
-
const valuesToRemove = new Set(normalizeCsvValues(values));
|
|
954
|
-
const next = splitCsv(this.getActiveEntryClaim(key))
|
|
955
|
-
.filter((value) => !valuesToRemove.has(value));
|
|
956
|
-
return this.setActiveEntryClaimList(key, next);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* High-level factory for consent-access editing.
|
|
961
|
-
*
|
|
962
|
-
* Prefer this name in onboarding docs when the developer intent is:
|
|
963
|
-
* "edit a Consent access bundle carried by a Communication".
|
|
964
|
-
*/
|
|
965
|
-
export function createConsentAccessEditor(options = {}) {
|
|
966
|
-
return new ConsentAccessEditor(options);
|
|
967
|
-
}
|
|
968
|
-
function ensureEntryResource(entry, mode) {
|
|
969
|
-
const resource = entry.resource;
|
|
970
|
-
if (resource && typeof resource === 'object') {
|
|
971
|
-
return resource;
|
|
972
|
-
}
|
|
973
|
-
if (mode === 'normalize') {
|
|
974
|
-
return { meta: { claims: {} } };
|
|
975
|
-
}
|
|
976
|
-
throw new Error('Active entry does not contain a valid resource object.');
|
|
977
|
-
}
|
|
978
|
-
function validateBundleLike(bundle, mode) {
|
|
979
|
-
const looksLikeBundle = bundle && bundle.resourceType === ResourceTypesFhirR4.Bundle && Array.isArray(bundle.data);
|
|
980
|
-
if (looksLikeBundle) {
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
if (mode === 'normalize') {
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
throw new Error('Decoded attachment is not a valid BundleJsonApi payload.');
|
|
987
|
-
}
|
|
988
|
-
function createEmptyBundle() {
|
|
989
|
-
return {
|
|
990
|
-
resourceType: ResourceTypesFhirR4.Bundle,
|
|
991
|
-
type: 'batch',
|
|
992
|
-
data: [],
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
function encodeBundleToBase64(bundle) {
|
|
996
|
-
return Buffer.from(JSON.stringify(bundle), 'utf8').toString('base64');
|
|
997
|
-
}
|
|
998
|
-
function cloneBundle(bundle) {
|
|
999
|
-
return JSON.parse(JSON.stringify(bundle));
|
|
1000
|
-
}
|
|
1001
|
-
function cloneEntry(entry) {
|
|
1002
|
-
return JSON.parse(JSON.stringify(entry));
|
|
1003
|
-
}
|
|
1004
|
-
function cloneUnknownValue(value) {
|
|
1005
|
-
if (value === undefined) {
|
|
1006
|
-
return value;
|
|
1007
|
-
}
|
|
1008
|
-
return JSON.parse(JSON.stringify(value));
|
|
1009
|
-
}
|
|
1010
|
-
function asTrimmedString(value) {
|
|
1011
|
-
if (value === undefined || value === null) {
|
|
1012
|
-
return '';
|
|
1013
|
-
}
|
|
1014
|
-
return String(value).trim();
|
|
1015
|
-
}
|
|
1016
|
-
function resolveContainedDocumentsClaimKey(resourceType) {
|
|
1017
|
-
if (resourceType === ResourceTypesFhirR4.Consent) {
|
|
1018
|
-
return ClaimConsent.containedDocuments;
|
|
1019
|
-
}
|
|
1020
|
-
if (resourceType === ResourceTypesFhirR4.MedicationStatement) {
|
|
1021
|
-
return MedicationStatementClaim.ContainedDocuments;
|
|
1022
|
-
}
|
|
1023
|
-
if (resourceType === ResourceTypesFhirR4.Condition) {
|
|
1024
|
-
return ConditionClaim.ContainedDocuments;
|
|
1025
|
-
}
|
|
1026
|
-
if (resourceType === ResourceTypesFhirR4.AllergyIntolerance) {
|
|
1027
|
-
return AllergyIntoleranceClaim.ContainedDocuments;
|
|
1028
|
-
}
|
|
1029
|
-
return '';
|
|
1030
|
-
}
|
|
1031
|
-
function resolveSubjectFromClaims(claims) {
|
|
1032
|
-
return asTrimmedString(claims[ClaimConsent.subject]
|
|
1033
|
-
|| claims[MedicationStatementClaim.Subject]
|
|
1034
|
-
|| claims[ConditionClaim.Subject]
|
|
1035
|
-
|| claims[AllergyIntoleranceClaim.Subject]
|
|
1036
|
-
|| claims[AllergyIntoleranceClaim.Patient]
|
|
1037
|
-
|| claims[DocumentReferenceClaim.Subject]);
|
|
1038
|
-
}
|
|
1039
|
-
function setIfMissing(target, key, value) {
|
|
1040
|
-
if (target[key] !== undefined) {
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
if (value === undefined || value === null || String(value).trim() === '') {
|
|
1044
|
-
return;
|
|
1045
|
-
}
|
|
1046
|
-
target[key] = value;
|
|
1047
|
-
}
|
|
1048
|
-
function runtimeUuid(prefix) {
|
|
1049
|
-
const cryptoLike = globalThis;
|
|
1050
|
-
if (typeof cryptoLike.crypto?.randomUUID === 'function') {
|
|
1051
|
-
return cryptoLike.crypto.randomUUID();
|
|
1052
|
-
}
|
|
1053
|
-
return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
1054
|
-
}
|
|
1055
|
-
const CSV_SEPARATOR = ',';
|
|
1056
|
-
const DID_WEB_PREFIX = 'did:web:';
|
|
1057
|
-
const PHONE_PREFIX = 'tel:';
|
|
1058
|
-
function splitCsv(value) {
|
|
1059
|
-
return normalizeCsvValues(String(value || '').split(CSV_SEPARATOR));
|
|
1060
|
-
}
|
|
1061
|
-
function normalizeCsvValues(values) {
|
|
1062
|
-
return Array.from(new Set(values
|
|
1063
|
-
.map((item) => String(item || '').trim())
|
|
1064
|
-
.filter(Boolean)));
|
|
1065
|
-
}
|
|
1066
|
-
function buildClassifiedConsentTarget(kind, code, scopeCodes, sectionFamily) {
|
|
1067
|
-
const normalizedCode = String(code || '').trim();
|
|
1068
|
-
return {
|
|
1069
|
-
target: {
|
|
1070
|
-
kind,
|
|
1071
|
-
code: normalizedCode,
|
|
1072
|
-
display: resolveConsentTargetDisplay(kind, normalizedCode),
|
|
1073
|
-
...(kind === ConsentEditorTargetKinds.Section
|
|
1074
|
-
? { sectionFamily: sectionFamily || getHealthcareSectionFamilyByCode(normalizedCode) }
|
|
1075
|
-
: {}),
|
|
1076
|
-
},
|
|
1077
|
-
scopes: normalizeScopeCodes(scopeCodes).map((scopeCode) => ({
|
|
1078
|
-
code: scopeCode,
|
|
1079
|
-
display: resolveConsentScopeDisplay(scopeCode),
|
|
1080
|
-
})),
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1083
|
-
function buildSectionCatalogOptions(family) {
|
|
1084
|
-
return Object.values(getHealthcareSectionsByFamily(family)).map((descriptor) => buildClassifiedConsentTarget(ConsentEditorTargetKinds.Section, descriptor.attributeValue, [ConsentEditorScopeCodes.Read]));
|
|
1085
|
-
}
|
|
1086
|
-
function normalizeScopeCodes(scopeCodes) {
|
|
1087
|
-
const values = Array.from(new Set(scopeCodes.map((scopeCode) => String(scopeCode).trim()).filter(Boolean)));
|
|
1088
|
-
return values.filter((scopeCode) => Object.values(ConsentEditorScopeCodes).includes(scopeCode));
|
|
1089
|
-
}
|
|
1090
|
-
function normalizeClassifiedTargets(targets) {
|
|
1091
|
-
const result = new Map();
|
|
1092
|
-
for (const target of targets) {
|
|
1093
|
-
const normalized = buildClassifiedConsentTarget(target.target.kind, String(target.target.code || '').trim(), target.scopes.map((scope) => scope.code), target.target.sectionFamily);
|
|
1094
|
-
const key = `${normalized.target.kind}:${normalized.target.code}`;
|
|
1095
|
-
result.set(key, normalized);
|
|
1096
|
-
}
|
|
1097
|
-
return Array.from(result.values());
|
|
1098
|
-
}
|
|
1099
|
-
function resolveConsentTargetDisplay(kind, code) {
|
|
1100
|
-
if (kind === ConsentEditorTargetKinds.ResourceType) {
|
|
1101
|
-
return code;
|
|
1102
|
-
}
|
|
1103
|
-
if (kind === ConsentEditorTargetKinds.Section) {
|
|
1104
|
-
const loincCode = code.includes('|') ? code.split('|').slice(-1)[0] : code;
|
|
1105
|
-
return getHealthcareSectionByCode(loincCode)?.titleEn;
|
|
1106
|
-
}
|
|
1107
|
-
return undefined;
|
|
1108
|
-
}
|
|
1109
|
-
function resolveConsentScopeDisplay(scopeCode) {
|
|
1110
|
-
if (scopeCode === ConsentEditorScopeCodes.Search)
|
|
1111
|
-
return 'search';
|
|
1112
|
-
if (scopeCode === ConsentEditorScopeCodes.Read)
|
|
1113
|
-
return 'read';
|
|
1114
|
-
if (scopeCode === ConsentEditorScopeCodes.Create)
|
|
1115
|
-
return 'create';
|
|
1116
|
-
if (scopeCode === ConsentEditorScopeCodes.Update)
|
|
1117
|
-
return 'update';
|
|
1118
|
-
return 'delete';
|
|
1119
|
-
}
|
|
1120
|
-
function resolveConsentPurposeDisplay(code) {
|
|
1121
|
-
const normalized = String(code || '').trim();
|
|
1122
|
-
if (normalized === HealthcareConsentPurposes.Treatment) {
|
|
1123
|
-
return 'Treatment';
|
|
1124
|
-
}
|
|
1125
|
-
if (normalized === HealthcareConsentPurposes.EmergencyTreatment) {
|
|
1126
|
-
return 'Emergency treatment';
|
|
1127
|
-
}
|
|
1128
|
-
if (normalized === HealthcareConsentPurposes.CareManagement) {
|
|
1129
|
-
return 'Care management';
|
|
1130
|
-
}
|
|
1131
|
-
if (normalized === HealthcareConsentPurposes.Operations) {
|
|
1132
|
-
return 'Operations';
|
|
1133
|
-
}
|
|
1134
|
-
if (normalized === HealthcareConsentPurposes.PatientAdministration) {
|
|
1135
|
-
return 'Patient administration';
|
|
1136
|
-
}
|
|
1137
|
-
if (normalized === HealthcareConsentPurposes.RecordsManagement) {
|
|
1138
|
-
return 'Records management';
|
|
1139
|
-
}
|
|
1140
|
-
return normalized;
|
|
1141
|
-
}
|
|
1142
|
-
function looksLikeEmailToken(value) {
|
|
1143
|
-
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value || '').trim().toLowerCase());
|
|
1144
|
-
}
|
|
1145
|
-
function looksLikePhoneToken(value) {
|
|
1146
|
-
return String(value || '').trim().startsWith(PHONE_PREFIX);
|
|
1147
|
-
}
|
|
1148
|
-
function looksLikeJurisdictionToken(value) {
|
|
1149
|
-
return /^[A-Z]{2}([\-:][A-Z0-9]+)?$/.test(String(value || '').trim());
|
|
1150
|
-
}
|
|
1151
|
-
function parseDidWebOrganizationToken(value) {
|
|
1152
|
-
const normalized = String(value || '').trim();
|
|
1153
|
-
if (!normalized.startsWith(DID_WEB_PREFIX)) {
|
|
1154
|
-
return undefined;
|
|
1155
|
-
}
|
|
1156
|
-
const segments = normalized.slice(DID_WEB_PREFIX.length).split(':').filter(Boolean);
|
|
1157
|
-
if (segments.length === 0) {
|
|
1158
|
-
return undefined;
|
|
1159
|
-
}
|
|
1160
|
-
const [domain, ...departmentSegments] = segments;
|
|
1161
|
-
return {
|
|
1162
|
-
domain,
|
|
1163
|
-
display: domain,
|
|
1164
|
-
departments: departmentSegments.map((segment) => ({
|
|
1165
|
-
code: segment,
|
|
1166
|
-
display: segment,
|
|
1167
|
-
})),
|
|
1168
|
-
locations: [],
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
function parseConsentActorRole(value) {
|
|
1172
|
-
const normalized = String(value || '').trim();
|
|
1173
|
-
if (!normalized) {
|
|
1174
|
-
return undefined;
|
|
1175
|
-
}
|
|
1176
|
-
const healthcareRole = getHealthcareRoleByClaim(normalized);
|
|
1177
|
-
if (healthcareRole) {
|
|
1178
|
-
return {
|
|
1179
|
-
codingSystem: healthcareRole.codingSystem,
|
|
1180
|
-
code: healthcareRole.code,
|
|
1181
|
-
display: healthcareRole.titleEn,
|
|
1182
|
-
};
|
|
1183
|
-
}
|
|
1184
|
-
const separatorIndex = normalized.indexOf('|');
|
|
1185
|
-
if (separatorIndex < 0) {
|
|
1186
|
-
return {
|
|
1187
|
-
codingSystem: '',
|
|
1188
|
-
code: normalized,
|
|
1189
|
-
display: normalized,
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
return {
|
|
1193
|
-
codingSystem: normalized.slice(0, separatorIndex),
|
|
1194
|
-
code: normalized.slice(separatorIndex + 1),
|
|
1195
|
-
display: normalized,
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
|
-
function flattenClassifiedActors(classifiedActors) {
|
|
1199
|
-
return normalizeCsvValues([
|
|
1200
|
-
...classifiedActors.jurisdictions.map((jurisdiction) => jurisdiction.code),
|
|
1201
|
-
...classifiedActors.organizations.map(serializeClassifiedOrganization),
|
|
1202
|
-
...classifiedActors.users.flatMap((user) => [user.email, user.phone]),
|
|
1203
|
-
]);
|
|
1204
|
-
}
|
|
1205
|
-
function serializeClassifiedOrganization(organization) {
|
|
1206
|
-
const domain = String(organization.domain || '').trim();
|
|
1207
|
-
const departments = organization.departments
|
|
1208
|
-
.map((department) => String(department.code || '').trim())
|
|
1209
|
-
.filter(Boolean);
|
|
1210
|
-
return [DID_WEB_PREFIX.replace(/:$/, ''), domain, ...departments].join(':');
|
|
1211
|
-
}
|
|
1212
|
-
function flattenClassifiedRoles(classifiedRoles) {
|
|
1213
|
-
return normalizeCsvValues([
|
|
1214
|
-
...classifiedRoles.professional.map(serializeClassifiedRole),
|
|
1215
|
-
...classifiedRoles.relationship.map(serializeClassifiedRole),
|
|
1216
|
-
...classifiedRoles.legalRepresentative.map(serializeClassifiedRole),
|
|
1217
|
-
...classifiedRoles.other.map(serializeClassifiedRole),
|
|
1218
|
-
]);
|
|
1219
|
-
}
|
|
1220
|
-
function serializeClassifiedRole(role) {
|
|
1221
|
-
const descriptor = getHealthcareRoleByClaim(`${String(role.codingSystem || '').trim()}|${String(role.code || '').trim()}`);
|
|
1222
|
-
if (descriptor) {
|
|
1223
|
-
return descriptor.claim;
|
|
1224
|
-
}
|
|
1225
|
-
return String(role.code || '').trim() || String(role.display || '').trim();
|
|
1226
|
-
}
|
|
1227
|
-
function flattenClassifiedTargets(classifiedTargets) {
|
|
1228
|
-
const coreSections = [];
|
|
1229
|
-
const kindOfDocuments = [];
|
|
1230
|
-
const typeOfServices = [];
|
|
1231
|
-
const subjectMatterDomains = [];
|
|
1232
|
-
const resourceTypes = [];
|
|
1233
|
-
for (const entry of normalizeClassifiedTargets(classifiedTargets)) {
|
|
1234
|
-
if (entry.target.kind === ConsentEditorTargetKinds.ResourceType) {
|
|
1235
|
-
resourceTypes.push(entry.target.code);
|
|
1236
|
-
continue;
|
|
1237
|
-
}
|
|
1238
|
-
const family = entry.target.sectionFamily || getHealthcareSectionFamilyByCode(entry.target.code);
|
|
1239
|
-
if (family === HealthcareCanonicalSectionFamilies.KindOfDocument) {
|
|
1240
|
-
kindOfDocuments.push(entry.target.code);
|
|
1241
|
-
continue;
|
|
1242
|
-
}
|
|
1243
|
-
if (family === HealthcareCanonicalSectionFamilies.TypeOfService) {
|
|
1244
|
-
typeOfServices.push(entry.target.code);
|
|
1245
|
-
continue;
|
|
1246
|
-
}
|
|
1247
|
-
if (family === HealthcareCanonicalSectionFamilies.SubjectMatterDomain) {
|
|
1248
|
-
subjectMatterDomains.push(entry.target.code);
|
|
1249
|
-
continue;
|
|
1250
|
-
}
|
|
1251
|
-
coreSections.push(entry.target.code);
|
|
1252
|
-
}
|
|
1253
|
-
return {
|
|
1254
|
-
coreSections: normalizeCsvValues(coreSections),
|
|
1255
|
-
kindOfDocuments: normalizeCsvValues(kindOfDocuments),
|
|
1256
|
-
typeOfServices: normalizeCsvValues(typeOfServices),
|
|
1257
|
-
subjectMatterDomains: normalizeCsvValues(subjectMatterDomains),
|
|
1258
|
-
resourceTypes: normalizeCsvValues(resourceTypes),
|
|
1259
|
-
};
|
|
1260
|
-
}
|