gdc-common-utils-ts 2.1.0 → 2.3.0
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 +22 -2
- 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/data-collections.js +0 -1
- package/dist/constants/didcomm.d.ts +22 -0
- package/dist/constants/didcomm.js +22 -0
- package/dist/constants/fhir-resource-types.d.ts +16 -0
- package/dist/constants/fhir-resource-types.js +7 -0
- package/dist/constants/healthcare.d.ts +348 -193
- package/dist/constants/healthcare.js +99 -37
- package/dist/constants/hl7-roles.d.ts +38 -4
- package/dist/constants/hl7-roles.js +92 -4
- 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/i18n/role-codes.i18n.d.ts +9 -1
- package/dist/i18n/role-codes.i18n.js +12 -1
- 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/device-license.d.ts +20 -0
- 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/models/interoperable-claims/related-person-claims.d.ts +13 -0
- package/dist/models/interoperable-claims/related-person-claims.js +16 -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 +36 -0
- package/dist/utils/bundle-document-builder.js +310 -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/clinical-resource-view.d.ts +16 -0
- package/dist/utils/clinical-resource-view.js +267 -33
- 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-consent-access-editor.d.ts +8 -4
- package/dist/utils/communication-consent-access-editor.js +8 -4
- 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/consent-duplicate-rules.js +1 -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/license-list-search.d.ts +5 -0
- package/dist/utils/license-list-search.js +9 -0
- package/dist/utils/license.d.ts +45 -1
- package/dist/utils/license.js +86 -2
- 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/related-person-list.d.ts +4 -0
- package/dist/utils/related-person-list.js +26 -6
- 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
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { buildOrganizationDidWeb, getBaseUrlFromDidWeb } from './did.js';
|
|
3
|
+
function normalizeString(value) {
|
|
4
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
5
|
+
}
|
|
6
|
+
function encodeDidWebAuthority(authority) {
|
|
7
|
+
return authority.replace(/:/g, '%3A').toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
function normalizeAuthorityHost(value) {
|
|
10
|
+
const raw = normalizeString(value).replace(/\/+$/, '');
|
|
11
|
+
if (!raw)
|
|
12
|
+
return '';
|
|
13
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
14
|
+
try {
|
|
15
|
+
return new URL(raw).host;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return raw.replace(/^https?:\/\//i, '');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return raw;
|
|
22
|
+
}
|
|
23
|
+
function protocolForAuthority(authority) {
|
|
24
|
+
const normalized = authority.trim().toLowerCase();
|
|
25
|
+
return normalized.startsWith('localhost')
|
|
26
|
+
|| normalized.startsWith('127.0.0.1')
|
|
27
|
+
|| normalized.startsWith('[::1]')
|
|
28
|
+
? 'http'
|
|
29
|
+
: 'https';
|
|
30
|
+
}
|
|
31
|
+
function inferMatchedBy(input) {
|
|
32
|
+
if (normalizeString(input.subjectDid))
|
|
33
|
+
return 'subject-did';
|
|
34
|
+
if (normalizeString(input.subjectSameAs))
|
|
35
|
+
return 'subject-same-as';
|
|
36
|
+
if (normalizeString(input.authorityDidWeb))
|
|
37
|
+
return 'authority-did';
|
|
38
|
+
if (normalizeString(input.authorityBaseUrl))
|
|
39
|
+
return 'authority-base-url';
|
|
40
|
+
return 'tenant-context';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Builds one authority/root `did:web` from a bare host authority or URL.
|
|
44
|
+
*
|
|
45
|
+
* Examples:
|
|
46
|
+
* - `https://gw.example.org` -> `did:web:gw.example.org`
|
|
47
|
+
* - `localhost:3300` -> `did:web:localhost%3A3300`
|
|
48
|
+
*/
|
|
49
|
+
export function buildAuthorityDidWeb(input) {
|
|
50
|
+
const authority = normalizeAuthorityHost(input.authority);
|
|
51
|
+
if (!authority)
|
|
52
|
+
throw new Error('buildAuthorityDidWeb requires authority.');
|
|
53
|
+
return `did:web:${encodeDidWebAuthority(authority)}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Normalizes one authority base URL from either an explicit URL or one
|
|
57
|
+
* authority/root `did:web`.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveAuthorityBaseUrl(input) {
|
|
60
|
+
const direct = normalizeString(input.authorityBaseUrl);
|
|
61
|
+
if (direct) {
|
|
62
|
+
const host = normalizeAuthorityHost(direct);
|
|
63
|
+
return host ? `${protocolForAuthority(host)}://${host}/` : undefined;
|
|
64
|
+
}
|
|
65
|
+
const did = normalizeString(input.authorityDidWeb);
|
|
66
|
+
if (!did)
|
|
67
|
+
return undefined;
|
|
68
|
+
return getBaseUrlFromDidWeb(did);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Builds one compatibility authority resolution when the caller only knows:
|
|
72
|
+
* - tenant context
|
|
73
|
+
* - one base URL or one root authority `did:web`
|
|
74
|
+
*
|
|
75
|
+
* This helper intentionally keeps the current hosted-DID fallback in one place
|
|
76
|
+
* so app code does not have to hand-invent `did:web` strings.
|
|
77
|
+
*/
|
|
78
|
+
export function buildLegacyAuthorityResolution(input) {
|
|
79
|
+
const authorityDidWeb = normalizeString(input.authorityDidWeb)
|
|
80
|
+
|| buildAuthorityDidWeb({ authority: normalizeString(input.authorityBaseUrl) });
|
|
81
|
+
const authorityBaseUrl = resolveAuthorityBaseUrl({
|
|
82
|
+
authorityBaseUrl: input.authorityBaseUrl,
|
|
83
|
+
authorityDidWeb,
|
|
84
|
+
});
|
|
85
|
+
const tenantDidWeb = buildOrganizationDidWeb({
|
|
86
|
+
hostDidWeb: authorityDidWeb,
|
|
87
|
+
tenantId: input.tenantId,
|
|
88
|
+
jurisdiction: input.jurisdiction,
|
|
89
|
+
version: normalizeString(input.version) || 'v1',
|
|
90
|
+
sector: input.sector,
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
authorityDidWeb,
|
|
94
|
+
authorityBaseUrl,
|
|
95
|
+
tenantDidWeb,
|
|
96
|
+
metadataUrl: normalizeString(input.metadataUrl) || undefined,
|
|
97
|
+
source: 'legacy',
|
|
98
|
+
matchedBy: inferMatchedBy(input),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Converts one resolution into a catalog-style record so runtimes can preload
|
|
103
|
+
* legacy fallback data into a shared resolver cache.
|
|
104
|
+
*/
|
|
105
|
+
export function authorityResolutionToCatalogRecord(resolution) {
|
|
106
|
+
return {
|
|
107
|
+
authorityDidWeb: resolution.authorityDidWeb,
|
|
108
|
+
authorityBaseUrl: resolution.authorityBaseUrl,
|
|
109
|
+
tenantDidWeb: resolution.tenantDidWeb,
|
|
110
|
+
metadataUrl: resolution.metadataUrl,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -17,6 +17,17 @@ export type BackendMessageManagerMemOptions = Readonly<{
|
|
|
17
17
|
*
|
|
18
18
|
* This is the lowest reusable BFF/proxy slice before actor-specific SDK
|
|
19
19
|
* facades appear.
|
|
20
|
+
*
|
|
21
|
+
* Normal deployment story:
|
|
22
|
+
* - the injected wallet is the unlocked wallet of the user profile runtime
|
|
23
|
+
* - that same profile wallet usually encrypts outbound messages and decrypts
|
|
24
|
+
* replies
|
|
25
|
+
*
|
|
26
|
+
* Optional deployment story:
|
|
27
|
+
* - one BFF/proxy may also own a separate service wallet for its own
|
|
28
|
+
* envelopes, signatures, confidential storage, or tenant-level messaging
|
|
29
|
+
* - that service wallet is a different actor and must not be confused with
|
|
30
|
+
* the user-profile wallet handled here
|
|
20
31
|
*/
|
|
21
32
|
export declare class BackendMessageManagerMem {
|
|
22
33
|
private readonly wallet;
|
|
@@ -12,6 +12,17 @@ function clone(value) {
|
|
|
12
12
|
*
|
|
13
13
|
* This is the lowest reusable BFF/proxy slice before actor-specific SDK
|
|
14
14
|
* facades appear.
|
|
15
|
+
*
|
|
16
|
+
* Normal deployment story:
|
|
17
|
+
* - the injected wallet is the unlocked wallet of the user profile runtime
|
|
18
|
+
* - that same profile wallet usually encrypts outbound messages and decrypts
|
|
19
|
+
* replies
|
|
20
|
+
*
|
|
21
|
+
* Optional deployment story:
|
|
22
|
+
* - one BFF/proxy may also own a separate service wallet for its own
|
|
23
|
+
* envelopes, signatures, confidential storage, or tenant-level messaging
|
|
24
|
+
* - that service wallet is a different actor and must not be confused with
|
|
25
|
+
* the user-profile wallet handled here
|
|
15
26
|
*/
|
|
16
27
|
export class BackendMessageManagerMem {
|
|
17
28
|
wallet;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Copyright 2026 Conectate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
|
|
2
|
+
import { DidcommMessageTypes } from '../constants/didcomm.js';
|
|
2
3
|
export const BundleDidcommEntryTypes = Object.freeze({
|
|
3
|
-
Batch:
|
|
4
|
-
Document:
|
|
4
|
+
Batch: DidcommMessageTypes.BundleBatchRequest,
|
|
5
|
+
Document: DidcommMessageTypes.BundleDocumentRequest,
|
|
5
6
|
});
|
|
6
7
|
function clone(value) {
|
|
7
8
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -4,6 +4,7 @@ export type BuildBundleDocumentFromClaimsInput = Readonly<{
|
|
|
4
4
|
claimsList: readonly BundleDocumentClaims[];
|
|
5
5
|
subjectDid?: string;
|
|
6
6
|
compositionType?: string;
|
|
7
|
+
compositionClaims?: BundleDocumentClaims;
|
|
7
8
|
}>;
|
|
8
9
|
export type ValidateBundleDocumentResult = Readonly<{
|
|
9
10
|
ok: boolean;
|
|
@@ -13,8 +14,43 @@ export type BundleDocumentResourceFilter = Readonly<{
|
|
|
13
14
|
section?: string;
|
|
14
15
|
resourceType?: string;
|
|
15
16
|
}>;
|
|
17
|
+
export type PrepareBundleDocumentForSubjectOptions = Readonly<{
|
|
18
|
+
context?: string;
|
|
19
|
+
}>;
|
|
16
20
|
export declare function getSimpleClaimAttributeName(key: string): string;
|
|
17
21
|
export declare function extractFlatClaimValue(record: Record<string, any> | undefined, key: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Validates and prepares an imported FHIR document for one authorized subject.
|
|
24
|
+
*
|
|
25
|
+
* The input is cloned. Every resource receives a deterministic flat
|
|
26
|
+
* `meta.claims` projection. Resources referenced by `Composition.section`
|
|
27
|
+
* receive the canonical section tokens in `Composition.section`, and patient
|
|
28
|
+
* subject references are rebound to the selected `did:web` subject. Existing
|
|
29
|
+
* claims are retained, except subject/patient values are forced to the
|
|
30
|
+
* selected subject so an imported local `Patient/{id}` cannot escape the
|
|
31
|
+
* controller-authorized index scope. The source object is never mutated.
|
|
32
|
+
*
|
|
33
|
+
* This is a conversion primitive only. It neither confirms that a mismatched
|
|
34
|
+
* Patient is the right person nor persists anything; confirmation belongs to
|
|
35
|
+
* the product UI and transport/authorization remain GW concerns.
|
|
36
|
+
*/
|
|
37
|
+
export declare function prepareBundleDocumentForSubject(bundle: Record<string, any>, subjectDid: string, options?: PrepareBundleDocumentForSubjectOptions): Record<string, any>;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the Patient Full Name addressed by the document Composition.
|
|
40
|
+
*
|
|
41
|
+
* Resolution prefers the Patient referenced by `Composition.subject`, then
|
|
42
|
+
* falls back to the first Patient for compatibility with incomplete imports.
|
|
43
|
+
* Name selection prefers `use=official`, then `usual`, then the first name;
|
|
44
|
+
* `HumanName.text` wins over assembling prefix/given/family/suffix.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getBundleDocumentPatientFullName(bundle: Record<string, any>): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Temporary Full Name comparison key: whitespace folding plus uppercase only.
|
|
49
|
+
* Diacritics and punctuation are intentionally preserved until the shared
|
|
50
|
+
* ICAO transliteration contract exists, so `JOSE` and `JOSÉ` do not compare as
|
|
51
|
+
* equal by accident.
|
|
52
|
+
*/
|
|
53
|
+
export declare function normalizeFullNameForComparison(value: unknown): string;
|
|
18
54
|
export declare function detectClaimsResourceType(claims: BundleDocumentClaims): string | undefined;
|
|
19
55
|
export declare function convertClaimsToFhirResource(claims: BundleDocumentClaims, version?: 'r4'): FhirResource;
|
|
20
56
|
/**
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
|
|
2
|
+
import { AllergyIntoleranceClaim } from '../models/interoperable-claims/allergy-intolerance-claims.js';
|
|
3
|
+
import { CompositionClaim } from '../models/interoperable-claims/composition-claims.js';
|
|
4
|
+
import { ConditionClaim } from '../models/interoperable-claims/condition-claims.js';
|
|
5
|
+
import { DiagnosticReportClaim } from '../models/interoperable-claims/diagnostic-report-claims.js';
|
|
6
|
+
import { MedicationStatementClaim } from '../models/interoperable-claims/medication-statement-claims.js';
|
|
7
|
+
import { ClaimConsent } from '../models/consent-rule.js';
|
|
2
8
|
import { allergyIntoleranceFlatToFhirR4, appointmentFlatToFhirR4, carePlanFlatToFhirR4, clinicalImpressionFlatToFhirR4, compositionFlatToFhirR4, conditionFlatToFhirR4, convertFhirResourceToClaims, coverageFlatToFhirR4, deviceFlatToFhirR4, documentReferenceFlatToFhirR4, diagnosticReportFlatToFhirR4, encounterFlatToFhirR4, flagFlatToFhirR4, flatClaimsToFhirResource, immunizationFlatToFhirR4, locationFlatToFhirR4, medicationStatementFlatToFhirR4, observationFromFlatToFhirR4, organizationFlatToFhirR4, procedureFlatToFhirR4, relatedPersonFlatToFhirR4, } from './clinical-resource-converters.js';
|
|
3
9
|
function asTrimmedString(value) {
|
|
4
10
|
if (value === undefined || value === null)
|
|
@@ -93,6 +99,219 @@ function normalizeSectionCode(value) {
|
|
|
93
99
|
return { code: system };
|
|
94
100
|
return { system, code };
|
|
95
101
|
}
|
|
102
|
+
function toCanonicalSectionToken(system, code) {
|
|
103
|
+
const normalizedSystem = asTrimmedString(system);
|
|
104
|
+
const normalizedCode = asTrimmedString(code);
|
|
105
|
+
if (!normalizedCode)
|
|
106
|
+
return '';
|
|
107
|
+
if (!normalizedSystem)
|
|
108
|
+
return normalizedCode;
|
|
109
|
+
return `${normalizedSystem === 'http://loinc.org' ? 'LOINC' : normalizedSystem}|${normalizedCode}`;
|
|
110
|
+
}
|
|
111
|
+
function resourceReferenceAliases(entry) {
|
|
112
|
+
const resource = entry?.resource;
|
|
113
|
+
const resourceType = asTrimmedString(resource?.resourceType);
|
|
114
|
+
const resourceId = asTrimmedString(resource?.id);
|
|
115
|
+
const fullUrl = asTrimmedString(entry?.fullUrl);
|
|
116
|
+
return Array.from(new Set([
|
|
117
|
+
fullUrl,
|
|
118
|
+
resourceType && resourceId ? `${resourceType}/${resourceId}` : '',
|
|
119
|
+
resourceId,
|
|
120
|
+
fullUrl ? fullUrl.split('/').slice(-2).join('/') : '',
|
|
121
|
+
].filter(Boolean)));
|
|
122
|
+
}
|
|
123
|
+
function sectionMembershipByReference(composition) {
|
|
124
|
+
const memberships = new Map();
|
|
125
|
+
const visit = (sections) => {
|
|
126
|
+
if (!Array.isArray(sections))
|
|
127
|
+
return;
|
|
128
|
+
for (const section of sections) {
|
|
129
|
+
const coding = Array.isArray(section?.code?.coding) ? section.code.coding[0] : undefined;
|
|
130
|
+
const token = toCanonicalSectionToken(coding?.system, coding?.code);
|
|
131
|
+
if (token && Array.isArray(section?.entry)) {
|
|
132
|
+
for (const item of section.entry) {
|
|
133
|
+
const reference = asTrimmedString(item?.reference);
|
|
134
|
+
if (!reference)
|
|
135
|
+
continue;
|
|
136
|
+
const aliases = [reference, reference.split('/').slice(-2).join('/'), reference.split('/').pop() || ''];
|
|
137
|
+
for (const alias of aliases.filter(Boolean)) {
|
|
138
|
+
memberships.set(alias, Array.from(new Set([...(memberships.get(alias) || []), token])));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
visit(section?.section);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
visit(composition?.section);
|
|
146
|
+
return memberships;
|
|
147
|
+
}
|
|
148
|
+
function allCompositionSectionTokens(composition) {
|
|
149
|
+
const tokens = [];
|
|
150
|
+
const visit = (sections) => {
|
|
151
|
+
if (!Array.isArray(sections))
|
|
152
|
+
return;
|
|
153
|
+
for (const section of sections) {
|
|
154
|
+
const coding = Array.isArray(section?.code?.coding) ? section.code.coding[0] : undefined;
|
|
155
|
+
const token = toCanonicalSectionToken(coding?.system, coding?.code);
|
|
156
|
+
if (token)
|
|
157
|
+
tokens.push(token);
|
|
158
|
+
visit(section?.section);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
visit(composition?.section);
|
|
162
|
+
return Array.from(new Set(tokens));
|
|
163
|
+
}
|
|
164
|
+
function replacePatientSubjectReference(resource, subjectDid) {
|
|
165
|
+
const resourceType = asTrimmedString(resource?.resourceType);
|
|
166
|
+
if (resourceType === ResourceTypesFhirR4.Composition || resource?.subject?.reference) {
|
|
167
|
+
resource.subject = { ...(resource.subject || {}), reference: subjectDid };
|
|
168
|
+
}
|
|
169
|
+
if (resource?.patient?.reference) {
|
|
170
|
+
resource.patient = { ...(resource.patient || {}), reference: subjectDid };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Validates and prepares an imported FHIR document for one authorized subject.
|
|
175
|
+
*
|
|
176
|
+
* The input is cloned. Every resource receives a deterministic flat
|
|
177
|
+
* `meta.claims` projection. Resources referenced by `Composition.section`
|
|
178
|
+
* receive the canonical section tokens in `Composition.section`, and patient
|
|
179
|
+
* subject references are rebound to the selected `did:web` subject. Existing
|
|
180
|
+
* claims are retained, except subject/patient values are forced to the
|
|
181
|
+
* selected subject so an imported local `Patient/{id}` cannot escape the
|
|
182
|
+
* controller-authorized index scope. The source object is never mutated.
|
|
183
|
+
*
|
|
184
|
+
* This is a conversion primitive only. It neither confirms that a mismatched
|
|
185
|
+
* Patient is the right person nor persists anything; confirmation belongs to
|
|
186
|
+
* the product UI and transport/authorization remain GW concerns.
|
|
187
|
+
*/
|
|
188
|
+
export function prepareBundleDocumentForSubject(bundle, subjectDid, options = {}) {
|
|
189
|
+
const validation = validateBundleDocumentBasic(bundle);
|
|
190
|
+
if (!validation.ok)
|
|
191
|
+
throw new Error(validation.issues.join(' '));
|
|
192
|
+
const normalizedSubjectDid = asTrimmedString(subjectDid);
|
|
193
|
+
if (!normalizedSubjectDid)
|
|
194
|
+
throw new Error('A subject DID is required.');
|
|
195
|
+
const prepared = JSON.parse(JSON.stringify(bundle));
|
|
196
|
+
const entries = prepared.entry;
|
|
197
|
+
const composition = entries[0]?.resource;
|
|
198
|
+
const membership = sectionMembershipByReference(composition);
|
|
199
|
+
const allSections = allCompositionSectionTokens(composition);
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
const resource = entry?.resource;
|
|
202
|
+
if (!resource || typeof resource !== 'object')
|
|
203
|
+
continue;
|
|
204
|
+
replacePatientSubjectReference(resource, normalizedSubjectDid);
|
|
205
|
+
const generated = convertFhirResourceToClaims(resource, options.context || 'org.hl7.fhir.r4');
|
|
206
|
+
const existing = resource?.meta?.claims;
|
|
207
|
+
const claims = {
|
|
208
|
+
...generated,
|
|
209
|
+
...(existing && typeof existing === 'object' && !Array.isArray(existing) ? existing : {}),
|
|
210
|
+
};
|
|
211
|
+
const resourceType = asTrimmedString(resource.resourceType);
|
|
212
|
+
if (resourceType !== 'Patient') {
|
|
213
|
+
if (resource?.subject?.reference)
|
|
214
|
+
claims[`${resourceType}.subject`] = normalizedSubjectDid;
|
|
215
|
+
if (resource?.patient?.reference)
|
|
216
|
+
claims[`${resourceType}.patient`] = normalizedSubjectDid;
|
|
217
|
+
}
|
|
218
|
+
const resourceSections = resourceType === ResourceTypesFhirR4.Composition
|
|
219
|
+
? allSections
|
|
220
|
+
: resourceReferenceAliases(entry).flatMap((alias) => membership.get(alias) || []);
|
|
221
|
+
if (resourceSections.length > 0) {
|
|
222
|
+
claims[CompositionClaim.Section] = Array.from(new Set(resourceSections)).join(',');
|
|
223
|
+
}
|
|
224
|
+
resource.meta = {
|
|
225
|
+
...(resource.meta && typeof resource.meta === 'object' ? resource.meta : {}),
|
|
226
|
+
claims,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return prepared;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Returns the Patient Full Name addressed by the document Composition.
|
|
233
|
+
*
|
|
234
|
+
* Resolution prefers the Patient referenced by `Composition.subject`, then
|
|
235
|
+
* falls back to the first Patient for compatibility with incomplete imports.
|
|
236
|
+
* Name selection prefers `use=official`, then `usual`, then the first name;
|
|
237
|
+
* `HumanName.text` wins over assembling prefix/given/family/suffix.
|
|
238
|
+
*/
|
|
239
|
+
export function getBundleDocumentPatientFullName(bundle) {
|
|
240
|
+
const entries = Array.isArray(bundle?.entry) ? bundle.entry : [];
|
|
241
|
+
const composition = entries[0]?.resource;
|
|
242
|
+
const subjectReference = asTrimmedString(composition?.subject?.reference);
|
|
243
|
+
const patientEntry = entries.find((entry) => {
|
|
244
|
+
if (entry?.resource?.resourceType !== 'Patient')
|
|
245
|
+
return false;
|
|
246
|
+
if (!subjectReference)
|
|
247
|
+
return true;
|
|
248
|
+
return resourceReferenceAliases(entry).some((alias) => alias === subjectReference
|
|
249
|
+
|| alias === subjectReference.split('/').slice(-2).join('/')
|
|
250
|
+
|| alias === subjectReference.split('/').pop());
|
|
251
|
+
}) || entries.find((entry) => entry?.resource?.resourceType === 'Patient');
|
|
252
|
+
const names = Array.isArray(patientEntry?.resource?.name) ? patientEntry.resource.name : [];
|
|
253
|
+
const name = names.find((item) => item?.use === 'official')
|
|
254
|
+
|| names.find((item) => item?.use === 'usual')
|
|
255
|
+
|| names[0];
|
|
256
|
+
if (!name)
|
|
257
|
+
return undefined;
|
|
258
|
+
const text = asTrimmedString(name.text);
|
|
259
|
+
if (text)
|
|
260
|
+
return text;
|
|
261
|
+
const parts = [
|
|
262
|
+
...(Array.isArray(name.prefix) ? name.prefix : []),
|
|
263
|
+
...(Array.isArray(name.given) ? name.given : []),
|
|
264
|
+
name.family,
|
|
265
|
+
...(Array.isArray(name.suffix) ? name.suffix : []),
|
|
266
|
+
].map(asTrimmedString).filter(Boolean);
|
|
267
|
+
return parts.length > 0 ? parts.join(' ') : undefined;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Temporary Full Name comparison key: whitespace folding plus uppercase only.
|
|
271
|
+
* Diacritics and punctuation are intentionally preserved until the shared
|
|
272
|
+
* ICAO transliteration contract exists, so `JOSE` and `JOSÉ` do not compare as
|
|
273
|
+
* equal by accident.
|
|
274
|
+
*/
|
|
275
|
+
export function normalizeFullNameForComparison(value) {
|
|
276
|
+
return asTrimmedString(value).replace(/\s+/g, ' ').toUpperCase();
|
|
277
|
+
}
|
|
278
|
+
function resolveContainedReferenceListClaimKey(resourceType) {
|
|
279
|
+
switch (resourceType) {
|
|
280
|
+
case ResourceTypesFhirR4.MedicationStatement:
|
|
281
|
+
return MedicationStatementClaim.ContainedReferenceList;
|
|
282
|
+
case ResourceTypesFhirR4.AllergyIntolerance:
|
|
283
|
+
return AllergyIntoleranceClaim.ContainedReferenceList;
|
|
284
|
+
case ResourceTypesFhirR4.Condition:
|
|
285
|
+
return ConditionClaim.ContainedReferenceList;
|
|
286
|
+
case ResourceTypesFhirR4.DiagnosticReport:
|
|
287
|
+
return DiagnosticReportClaim.ContainedReferenceList;
|
|
288
|
+
case ResourceTypesFhirR4.Consent:
|
|
289
|
+
return ClaimConsent.containedReferenceList;
|
|
290
|
+
default:
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function resolveContainedFlagClaimKey(resourceType) {
|
|
295
|
+
const normalized = asTrimmedString(resourceType);
|
|
296
|
+
return normalized ? `${normalized}.is-contained` : undefined;
|
|
297
|
+
}
|
|
298
|
+
function resolveContainedParentReferenceClaimKey(resourceType) {
|
|
299
|
+
const normalized = asTrimmedString(resourceType);
|
|
300
|
+
return normalized ? `${normalized}.contained-parent-reference` : undefined;
|
|
301
|
+
}
|
|
302
|
+
function mergeContainedResourceReferenceList(claims, references) {
|
|
303
|
+
const resourceType = detectClaimsResourceType(claims);
|
|
304
|
+
const claimKey = resourceType ? resolveContainedReferenceListClaimKey(resourceType) : undefined;
|
|
305
|
+
if (!claimKey || references.length === 0) {
|
|
306
|
+
return claims;
|
|
307
|
+
}
|
|
308
|
+
const current = splitCsv(claims[claimKey]);
|
|
309
|
+
const next = Array.from(new Set([...current, ...references].map((item) => asTrimmedString(item)).filter(Boolean)));
|
|
310
|
+
return {
|
|
311
|
+
...claims,
|
|
312
|
+
[claimKey]: next.join(','),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
96
315
|
export function detectClaimsResourceType(claims) {
|
|
97
316
|
const keys = Object.keys(claims || {});
|
|
98
317
|
if (keys.some((key) => key.startsWith('MedicationStatement.')))
|
|
@@ -181,12 +400,47 @@ export function extractBundleDocumentClaimsList(bundle, context = 'org.hl7.fhir.
|
|
|
181
400
|
.map((entry) => entry?.resource)
|
|
182
401
|
.filter((resource) => resource && typeof resource === 'object')
|
|
183
402
|
.filter((resource) => !ignoredResourceTypes.has(String(resource.resourceType || '')))
|
|
184
|
-
.
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
403
|
+
.flatMap((resource) => {
|
|
404
|
+
const resourceRecord = resource;
|
|
405
|
+
const resourceReference = `${asTrimmedString(resourceRecord.resourceType)}/${asTrimmedString(resourceRecord.id)}`;
|
|
406
|
+
const metaClaims = resourceRecord?.meta?.claims;
|
|
407
|
+
const baseClaims = metaClaims && typeof metaClaims === 'object' && !Array.isArray(metaClaims)
|
|
408
|
+
? ensureClaimsIdentifier({ ...metaClaims }, resourceRecord)
|
|
409
|
+
: ensureClaimsIdentifier(convertFhirResourceToClaims(resourceRecord, context), resourceRecord);
|
|
410
|
+
const containedResources = Array.isArray(resourceRecord.contained)
|
|
411
|
+
? resourceRecord.contained
|
|
412
|
+
: [];
|
|
413
|
+
const containedClaimsList = containedResources.map((containedResource, index) => {
|
|
414
|
+
const containedMetaClaims = (containedResource?.meta?.claims);
|
|
415
|
+
const containedClaims = containedMetaClaims && typeof containedMetaClaims === 'object' && !Array.isArray(containedMetaClaims)
|
|
416
|
+
? ensureClaimsIdentifier({ ...containedMetaClaims }, containedResource)
|
|
417
|
+
: ensureClaimsIdentifier(convertFhirResourceToClaims(containedResource, context), containedResource);
|
|
418
|
+
const containedResourceType = asTrimmedString(containedResource.resourceType);
|
|
419
|
+
const containedFlagClaimKey = resolveContainedFlagClaimKey(containedResourceType);
|
|
420
|
+
const containedParentReferenceClaimKey = resolveContainedParentReferenceClaimKey(containedResourceType);
|
|
421
|
+
return {
|
|
422
|
+
...containedClaims,
|
|
423
|
+
...(containedFlagClaimKey ? { [containedFlagClaimKey]: true } : {}),
|
|
424
|
+
...(containedParentReferenceClaimKey ? { [containedParentReferenceClaimKey]: resourceReference } : {}),
|
|
425
|
+
...(asTrimmedString(containedResource.id) ? {} : {
|
|
426
|
+
[`${containedResource.resourceType}.identifier`]: `${resourceReference}#contained-${index + 1}`,
|
|
427
|
+
}),
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
const containedReferences = containedResources
|
|
431
|
+
.map((containedResource, index) => {
|
|
432
|
+
const containedType = asTrimmedString(containedResource.resourceType);
|
|
433
|
+
const containedId = asTrimmedString(containedResource.id) || `${resourceReference}#contained-${index + 1}`;
|
|
434
|
+
if (!containedType || !containedId) {
|
|
435
|
+
return '';
|
|
436
|
+
}
|
|
437
|
+
return `${containedType}/${containedId}`;
|
|
438
|
+
})
|
|
439
|
+
.filter(Boolean);
|
|
440
|
+
return [
|
|
441
|
+
mergeContainedResourceReferenceList(baseClaims, containedReferences),
|
|
442
|
+
...containedClaimsList,
|
|
443
|
+
];
|
|
190
444
|
});
|
|
191
445
|
}
|
|
192
446
|
export function resolveClaimsSectionList(claims) {
|
|
@@ -202,9 +456,17 @@ export function resolveClaimsSectionList(claims) {
|
|
|
202
456
|
return Array.from(new Set(values));
|
|
203
457
|
}
|
|
204
458
|
export function buildBundleDocumentFromClaims(input) {
|
|
205
|
-
const
|
|
459
|
+
const compositionClaims = { ...(input.compositionClaims || {}) };
|
|
460
|
+
const compositionType = asTrimmedString(input.compositionType || compositionClaims[CompositionClaim.Type]) || 'http://loinc.org|60591-5';
|
|
461
|
+
const compositionSubject = asTrimmedString(input.subjectDid || compositionClaims[CompositionClaim.Subject]);
|
|
462
|
+
const compositionIdentifier = asTrimmedString(compositionClaims[CompositionClaim.Identifier]);
|
|
463
|
+
const compositionTitle = asTrimmedString(compositionClaims[CompositionClaim.Title]);
|
|
464
|
+
const compositionDate = asTrimmedString(compositionClaims[CompositionClaim.Date]);
|
|
465
|
+
const compositionAuthorList = splitCsv(compositionClaims[CompositionClaim.Author]);
|
|
206
466
|
const compositionSections = new Map();
|
|
207
|
-
const
|
|
467
|
+
const visibleEntries = [];
|
|
468
|
+
const resourceByReference = new Map();
|
|
469
|
+
const containedChildrenByParentReference = new Map();
|
|
208
470
|
input.claimsList.forEach((claims, index) => {
|
|
209
471
|
const resource = convertClaimsToFhirResource(claims);
|
|
210
472
|
resource.meta = {
|
|
@@ -212,8 +474,23 @@ export function buildBundleDocumentFromClaims(input) {
|
|
|
212
474
|
claims: { ...claims },
|
|
213
475
|
};
|
|
214
476
|
ensureResourceIdentifier(resource, claims, index);
|
|
215
|
-
|
|
216
|
-
const
|
|
477
|
+
const resourceReference = `${resource.resourceType}/${asTrimmedString(resource.id)}`;
|
|
478
|
+
const containedFlagClaimKey = resolveContainedFlagClaimKey(resource.resourceType);
|
|
479
|
+
const containedParentReferenceClaimKey = resolveContainedParentReferenceClaimKey(resource.resourceType);
|
|
480
|
+
const isContained = containedFlagClaimKey
|
|
481
|
+
? (claims[containedFlagClaimKey] === true || claims[containedFlagClaimKey] === 'true')
|
|
482
|
+
: false;
|
|
483
|
+
const containedParentReference = containedParentReferenceClaimKey
|
|
484
|
+
? asTrimmedString(claims[containedParentReferenceClaimKey])
|
|
485
|
+
: '';
|
|
486
|
+
if (isContained && containedParentReference) {
|
|
487
|
+
const currentChildren = containedChildrenByParentReference.get(containedParentReference) || [];
|
|
488
|
+
containedChildrenByParentReference.set(containedParentReference, [...currentChildren, resource]);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
visibleEntries.push({ resource });
|
|
492
|
+
resourceByReference.set(resourceReference, resource);
|
|
493
|
+
const resourceRef = resourceReference;
|
|
217
494
|
resolveClaimsSectionList(claims).forEach((sectionValue) => {
|
|
218
495
|
const normalizedSection = asTrimmedString(sectionValue);
|
|
219
496
|
if (!normalizedSection)
|
|
@@ -230,6 +507,13 @@ export function buildBundleDocumentFromClaims(input) {
|
|
|
230
507
|
compositionSections.set(normalizedSection, section);
|
|
231
508
|
});
|
|
232
509
|
});
|
|
510
|
+
containedChildrenByParentReference.forEach((children, parentReference) => {
|
|
511
|
+
const parent = resourceByReference.get(parentReference);
|
|
512
|
+
if (!parent) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
parent.contained = [...(Array.isArray(parent.contained) ? parent.contained : []), ...children];
|
|
516
|
+
});
|
|
233
517
|
const [compositionSystem, compositionCode] = compositionType.split('|');
|
|
234
518
|
return {
|
|
235
519
|
resourceType: ResourceTypesFhirR4.Bundle,
|
|
@@ -238,8 +522,14 @@ export function buildBundleDocumentFromClaims(input) {
|
|
|
238
522
|
{
|
|
239
523
|
resource: {
|
|
240
524
|
resourceType: ResourceTypesFhirR4.Composition,
|
|
525
|
+
...(compositionIdentifier ? { id: compositionIdentifier } : {}),
|
|
241
526
|
status: 'final',
|
|
242
|
-
|
|
527
|
+
...(compositionIdentifier ? {
|
|
528
|
+
identifier: [{ value: compositionIdentifier }],
|
|
529
|
+
} : {}),
|
|
530
|
+
...(compositionSubject ? {
|
|
531
|
+
subject: { reference: compositionSubject },
|
|
532
|
+
} : {}),
|
|
243
533
|
type: {
|
|
244
534
|
coding: [
|
|
245
535
|
compositionCode
|
|
@@ -247,18 +537,24 @@ export function buildBundleDocumentFromClaims(input) {
|
|
|
247
537
|
: { code: compositionSystem },
|
|
248
538
|
],
|
|
249
539
|
},
|
|
540
|
+
...(compositionTitle ? { title: compositionTitle } : {}),
|
|
541
|
+
...(compositionDate ? { date: compositionDate } : {}),
|
|
542
|
+
...(compositionAuthorList.length > 0 ? {
|
|
543
|
+
author: compositionAuthorList.map((reference) => ({ reference })),
|
|
544
|
+
} : {}),
|
|
545
|
+
meta: { claims: compositionClaims },
|
|
250
546
|
section: Array.from(compositionSections.values()),
|
|
251
547
|
},
|
|
252
548
|
},
|
|
253
|
-
...(
|
|
549
|
+
...(compositionSubject
|
|
254
550
|
? [{
|
|
255
551
|
resource: {
|
|
256
552
|
resourceType: 'Patient',
|
|
257
|
-
id:
|
|
553
|
+
id: compositionSubject,
|
|
258
554
|
},
|
|
259
555
|
}]
|
|
260
556
|
: []),
|
|
261
|
-
...
|
|
557
|
+
...visibleEntries,
|
|
262
558
|
],
|
|
263
559
|
};
|
|
264
560
|
}
|