vet-sdk-core-ts 0.4.12 → 0.4.14
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 +28 -13
- package/dist/research-study.d.ts +58 -65
- package/dist/research-study.js +177 -106
- package/docs/101-RESEARCH-STUDY-INVITATIONS.md +57 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,12 +8,13 @@ owns gateway request construction. GW VET remains the policy authority.
|
|
|
8
8
|
|
|
9
9
|
## Research studies
|
|
10
10
|
|
|
11
|
-
`buildVeterinaryResearchStudyCreateEntry`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
`buildVeterinaryResearchStudyCreateEntry` accepts ResearchStudy business data
|
|
12
|
+
and projects it into the canonical JSON:API resource object: `resourceType`,
|
|
13
|
+
`id` and flat `resource.meta.claims`. It does not send native `status`, `title`,
|
|
14
|
+
`associatedParty` or `recruitment` objects to GW. Associated parties use one
|
|
15
|
+
independent flat-claim PATCH entry per party, so references, roles, periods and
|
|
16
|
+
classifiers cannot be mixed across employees. Native FHIR JSON is produced
|
|
17
|
+
only by an explicit later export/projection boundary.
|
|
17
18
|
|
|
18
19
|
Study participation does not itself authorize access. The separate
|
|
19
20
|
`buildVeterinaryResearchStudySmartAuthorization` request is limited to create,
|
|
@@ -33,13 +34,25 @@ separate JSON:API `Consent/_batch`. Every POST is fixed to `active`, `permit`,
|
|
|
33
34
|
ResearchStudy scope. The API does not accept caller-authored actions, so it
|
|
34
35
|
cannot be broadened to delete.
|
|
35
36
|
|
|
36
|
-
`buildVeterinaryResearchStudyPartyInvitations` creates exactly one
|
|
37
|
-
`Communication` per associated `PractitionerRole`.
|
|
38
|
-
an `application/
|
|
39
|
-
|
|
40
|
-
`
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
`buildVeterinaryResearchStudyPartyInvitations` creates exactly one claims-only
|
|
38
|
+
`Communication` per associated `PractitionerRole`. Its canonical attachment
|
|
39
|
+
claims carry an `application/vnd.api+json` primary document containing one
|
|
40
|
+
claims-only draft Consent. `Consent.source-reference` pins the ResearchStudy
|
|
41
|
+
and `Consent.resource-type` pins `ResearchSubject`. The draft requests only
|
|
42
|
+
create, read, update and search; it contains no delete permission and never
|
|
43
|
+
authorizes SMART access. Native FHIR Communication, Consent or Bundle objects
|
|
44
|
+
are accepted only by explicit import/projection/export adapters.
|
|
45
|
+
|
|
46
|
+
`buildVeterinaryResearchStudyGroupCreateEntry` and
|
|
47
|
+
`buildVeterinaryResearchStudyGroupMemberPatchEntries` build flat-claim Group
|
|
48
|
+
POST/PATCH entries for an enumerated professional review team. Every member is
|
|
49
|
+
a registered `PractitionerRole`; the exact twelve Group search claims are
|
|
50
|
+
derived from `vet-data-utils-ts/group`, and each member has its own PATCH entry.
|
|
51
|
+
The Group stays independent: individual professionals are related to the study
|
|
52
|
+
through separate `ResearchStudy.associatedParty` PATCH entries. It must not use
|
|
53
|
+
`ResearchStudy.recruitment.eligibility`, which represents research-subject
|
|
54
|
+
cohorts or eligibility criteria. Group grants no authorization, permission or
|
|
55
|
+
SMART scope; DCR and active Consent remain separate.
|
|
43
56
|
|
|
44
57
|
The complete controller-to-professional contract and a browser-safe snippet
|
|
45
58
|
are in
|
|
@@ -51,6 +64,8 @@ FHIR references:
|
|
|
51
64
|
- https://hl7.org/fhir/R5/researchsubject-search.html
|
|
52
65
|
- https://hl7.org/fhir/R5/communication.html
|
|
53
66
|
- https://hl7.org/fhir/R5/consent-definitions.html
|
|
67
|
+
- https://hl7.org/fhir/R5/group.html
|
|
68
|
+
- https://hl7.org/fhir/R5/group-search.html
|
|
54
69
|
|
|
55
70
|
## Reusable professional BFF
|
|
56
71
|
|
package/dist/research-study.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type ResearchStudyAssociatedParty } from 'vet-data-utils-ts/research-study';
|
|
1
|
+
import { type ResearchStudyAssociatedParty, type ResearchStudyR5FlatClaimsResource } from 'vet-data-utils-ts/research-study';
|
|
2
|
+
import { type GroupR5FlatClaimsResource, type GroupR5ResearchTeam } from 'vet-data-utils-ts/group';
|
|
2
3
|
import { HealthcareConsentPurposes } from 'gdc-common-utils-ts/constants/healthcare';
|
|
3
4
|
import { ResourceTypesFhirR5 } from 'gdc-common-utils-ts/constants/fhir-resource-types';
|
|
4
5
|
import { ClaimConsent, ConsentStatuses } from 'gdc-common-utils-ts/models/consent-rule';
|
|
@@ -13,46 +14,16 @@ export declare const VeterinaryResearchStudyInvitationClaimNames: Readonly<{
|
|
|
13
14
|
readonly Action: ClaimConsent.action;
|
|
14
15
|
readonly ResourceType: ClaimConsent.resourceType;
|
|
15
16
|
}>;
|
|
16
|
-
type FhirReference = Readonly<{
|
|
17
|
-
reference: string;
|
|
18
|
-
type?: string;
|
|
19
|
-
}>;
|
|
20
|
-
type FhirCoding = Readonly<{
|
|
21
|
-
system: string;
|
|
22
|
-
code: string;
|
|
23
|
-
}>;
|
|
24
|
-
type FhirCodeableConcept = Readonly<{
|
|
25
|
-
coding: readonly FhirCoding[];
|
|
26
|
-
}>;
|
|
27
17
|
export type VeterinaryResearchStudyDraftConsent = Readonly<{
|
|
28
18
|
resourceType: 'Consent';
|
|
29
19
|
id: string;
|
|
30
|
-
status: 'draft';
|
|
31
|
-
grantor: readonly FhirReference[];
|
|
32
|
-
controller: readonly FhirReference[];
|
|
33
|
-
grantee: readonly FhirReference[];
|
|
34
|
-
decision: 'permit';
|
|
35
|
-
provision: readonly Readonly<{
|
|
36
|
-
actor: readonly Readonly<{
|
|
37
|
-
reference: FhirReference;
|
|
38
|
-
}>[];
|
|
39
|
-
action: readonly FhirCodeableConcept[];
|
|
40
|
-
purpose: readonly FhirCoding[];
|
|
41
|
-
resourceType: readonly FhirCoding[];
|
|
42
|
-
data: readonly Readonly<{
|
|
43
|
-
meaning: 'related';
|
|
44
|
-
reference: FhirReference;
|
|
45
|
-
}>[];
|
|
46
|
-
}>[];
|
|
47
20
|
meta: Readonly<{
|
|
48
21
|
claims: Readonly<Record<string, string>>;
|
|
49
22
|
}>;
|
|
50
23
|
}>;
|
|
51
|
-
export type
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
entry: readonly Readonly<{
|
|
55
|
-
fullUrl: string;
|
|
24
|
+
export type VeterinaryResearchStudyInvitationDocument = Readonly<{
|
|
25
|
+
data: readonly Readonly<{
|
|
26
|
+
type: 'Consent-v5.0.0';
|
|
56
27
|
resource: VeterinaryResearchStudyDraftConsent;
|
|
57
28
|
request: Readonly<{
|
|
58
29
|
method: 'POST';
|
|
@@ -63,22 +34,12 @@ export type VeterinaryResearchStudyInvitationBundle = Readonly<{
|
|
|
63
34
|
export type VeterinaryResearchStudyPartyInvitation = Readonly<{
|
|
64
35
|
resourceType: 'Communication';
|
|
65
36
|
id: string;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
about: readonly FhirReference[];
|
|
70
|
-
recipient: readonly FhirReference[];
|
|
71
|
-
sender: FhirReference;
|
|
72
|
-
sent?: string;
|
|
73
|
-
payload: readonly Readonly<{
|
|
74
|
-
contentAttachment: Readonly<{
|
|
75
|
-
contentType: 'application/fhir+json';
|
|
76
|
-
title: 'research-study-draft-consent.json';
|
|
77
|
-
data: string;
|
|
78
|
-
}>;
|
|
79
|
-
}>[];
|
|
37
|
+
meta: Readonly<{
|
|
38
|
+
claims: Readonly<Record<string, string>>;
|
|
39
|
+
}>;
|
|
80
40
|
}>;
|
|
81
|
-
|
|
41
|
+
/** Native-shaped builder input used only to derive the persisted flat claims. */
|
|
42
|
+
export type VeterinaryResearchStudySourceResource = Readonly<{
|
|
82
43
|
resourceType: 'ResearchStudy';
|
|
83
44
|
id: string;
|
|
84
45
|
status?: 'draft' | 'active' | 'retired' | 'unknown';
|
|
@@ -86,9 +47,10 @@ export type VeterinaryResearchStudyResource = Readonly<{
|
|
|
86
47
|
meta?: Readonly<Record<string, unknown>>;
|
|
87
48
|
[key: string]: unknown;
|
|
88
49
|
}>;
|
|
89
|
-
export type VeterinaryResearchStudyCreateResource =
|
|
90
|
-
status: NonNullable<
|
|
50
|
+
export type VeterinaryResearchStudyCreateResource = VeterinaryResearchStudySourceResource & Readonly<{
|
|
51
|
+
status: NonNullable<VeterinaryResearchStudySourceResource['status']>;
|
|
91
52
|
}>;
|
|
53
|
+
export type VeterinaryResearchStudyResource = ResearchStudyR5FlatClaimsResource;
|
|
92
54
|
export type VeterinaryResearchStudyBatchEntry = Readonly<{
|
|
93
55
|
type: 'ResearchStudy-v5.0.0';
|
|
94
56
|
resource: VeterinaryResearchStudyResource;
|
|
@@ -100,6 +62,18 @@ export type VeterinaryResearchStudyBatchEntry = Readonly<{
|
|
|
100
62
|
export type VeterinaryResearchStudyBatch = Readonly<{
|
|
101
63
|
data: readonly VeterinaryResearchStudyBatchEntry[];
|
|
102
64
|
}>;
|
|
65
|
+
export type VeterinaryResearchStudyGroupResource = GroupR5FlatClaimsResource;
|
|
66
|
+
export type VeterinaryResearchStudyGroupBatchEntry = Readonly<{
|
|
67
|
+
type: 'Group-v5.0.0';
|
|
68
|
+
resource: VeterinaryResearchStudyGroupResource;
|
|
69
|
+
request: Readonly<{
|
|
70
|
+
method: 'POST' | 'PATCH';
|
|
71
|
+
url: string;
|
|
72
|
+
}>;
|
|
73
|
+
}>;
|
|
74
|
+
export type VeterinaryResearchStudyGroupBatch = Readonly<{
|
|
75
|
+
data: readonly VeterinaryResearchStudyGroupBatchEntry[];
|
|
76
|
+
}>;
|
|
103
77
|
export type VeterinaryResearchStudySmartAuthorization = Readonly<{
|
|
104
78
|
purpose: typeof HealthcareConsentPurposes.Research;
|
|
105
79
|
researchStudyReference: string;
|
|
@@ -116,7 +90,6 @@ export type VeterinaryResearchStudyActiveConsentInput = Readonly<{
|
|
|
116
90
|
export type VeterinaryResearchStudyActiveConsent = Readonly<{
|
|
117
91
|
resourceType: typeof ResourceTypesFhirR5.Consent;
|
|
118
92
|
id: string;
|
|
119
|
-
status: typeof ConsentStatuses.Active;
|
|
120
93
|
meta: Readonly<{
|
|
121
94
|
claims: Readonly<Record<string, string>>;
|
|
122
95
|
}>;
|
|
@@ -160,16 +133,39 @@ export declare function buildVeterinaryResearchStudySmartAuthorization(input: Re
|
|
|
160
133
|
* @see https://hl7.org/fhir/R5/researchstudy.html
|
|
161
134
|
*/
|
|
162
135
|
export declare function buildVeterinaryResearchStudyActiveConsentBatch(inputs: readonly VeterinaryResearchStudyActiveConsentInput[]): VeterinaryResearchStudyActiveConsentBatch;
|
|
163
|
-
/** Builds one POST Consent entry accepted by the VET ResearchStudy manager. */
|
|
136
|
+
/** Builds one flat-claims POST Consent entry accepted by the VET ResearchStudy manager. */
|
|
164
137
|
export declare function buildVeterinaryResearchStudyActiveConsentEntry(input: VeterinaryResearchStudyActiveConsentInput): VeterinaryResearchStudyActiveConsentEntry;
|
|
165
138
|
export declare function buildVeterinaryResearchStudyBatch(entries: readonly VeterinaryResearchStudyBatchEntry[]): VeterinaryResearchStudyBatch;
|
|
139
|
+
/**
|
|
140
|
+
* Builds the flat-claims Group POST used to create one enumerated ResearchStudy
|
|
141
|
+
* review team. Every member is added later as its own PractitionerRole claim
|
|
142
|
+
* PATCH. Group remains a description only and cannot carry access policy.
|
|
143
|
+
*
|
|
144
|
+
* @see https://hl7.org/fhir/R5/group.html
|
|
145
|
+
* @see https://hl7.org/fhir/R5/group-search.html
|
|
146
|
+
*/
|
|
147
|
+
export declare function buildVeterinaryResearchStudyGroupCreateEntry(input: Readonly<{
|
|
148
|
+
group: GroupR5ResearchTeam;
|
|
149
|
+
}>): VeterinaryResearchStudyGroupBatchEntry;
|
|
150
|
+
/**
|
|
151
|
+
* Builds one independently correlated PATCH per PractitionerRole member. A
|
|
152
|
+
* PATCH changes descriptive membership only; DCR, Consent and SMART remain
|
|
153
|
+
* separate authorization boundaries.
|
|
154
|
+
*/
|
|
155
|
+
export declare function buildVeterinaryResearchStudyGroupMemberPatchEntries(input: Readonly<{
|
|
156
|
+
groupId: string;
|
|
157
|
+
members: unknown;
|
|
158
|
+
}>): readonly VeterinaryResearchStudyGroupBatchEntry[];
|
|
159
|
+
/** Wraps only validated Group POST/PATCH entries in one JSON:API document. */
|
|
160
|
+
export declare function buildVeterinaryResearchStudyGroupBatch(entries: readonly VeterinaryResearchStudyGroupBatchEntry[]): VeterinaryResearchStudyGroupBatch;
|
|
166
161
|
export declare function buildVeterinaryResearchStudyCreateEntry(input: Readonly<{
|
|
167
162
|
study: VeterinaryResearchStudyCreateResource;
|
|
168
163
|
}>): VeterinaryResearchStudyBatchEntry;
|
|
169
164
|
/**
|
|
170
165
|
* Builds one independent `_batch` PATCH entry per R5 associatedParty. Keeping
|
|
171
|
-
* each
|
|
172
|
-
* from being correlated with the wrong PractitionerRole.
|
|
166
|
+
* each party's claim map in its own entry prevents roles, periods and
|
|
167
|
+
* classifiers from being correlated with the wrong PractitionerRole. Native
|
|
168
|
+
* `associatedParty` JSON is not sent to persistence. These entries update
|
|
173
169
|
* study membership only; they do not grant CRUDS access.
|
|
174
170
|
*/
|
|
175
171
|
export declare function buildVeterinaryResearchStudyPartyPatchEntries(input: Readonly<{
|
|
@@ -178,17 +174,15 @@ export declare function buildVeterinaryResearchStudyPartyPatchEntries(input: Rea
|
|
|
178
174
|
}>): readonly VeterinaryResearchStudyBatchEntry[];
|
|
179
175
|
/**
|
|
180
176
|
* Builds the auditable R5 invitation sent to one ResearchStudy associated
|
|
181
|
-
* party. The Communication
|
|
182
|
-
*
|
|
177
|
+
* party. The claims-only Communication carries an `application/vnd.api+json`
|
|
178
|
+
* primary document with exactly one claims-only `Consent.status = draft`.
|
|
183
179
|
* Draft is descriptive only and can never satisfy authorization. The invitee
|
|
184
180
|
* must finish DCR and a controller must later persist a separate active
|
|
185
181
|
* Consent before the study-scoped SMART request can succeed.
|
|
186
182
|
*
|
|
187
|
-
*
|
|
188
|
-
* `
|
|
189
|
-
*
|
|
190
|
-
* search; their FHIR representation uses the matching REST interaction codes,
|
|
191
|
-
* where search is `search-type`. Delete is deliberately absent.
|
|
183
|
+
* The flat Consent claims retain the exact study boundary and affected
|
|
184
|
+
* `ResearchSubject` resource family. Delete is deliberately absent. Native
|
|
185
|
+
* FHIR projection belongs only at an explicit import/export boundary.
|
|
192
186
|
*
|
|
193
187
|
* @see https://hl7.org/fhir/R5/communication.html
|
|
194
188
|
* @see https://hl7.org/fhir/R5/consent.html
|
|
@@ -217,6 +211,5 @@ export declare function buildVeterinaryResearchStudyPartyInvitations(input: Read
|
|
|
217
211
|
consentId: string;
|
|
218
212
|
}>[];
|
|
219
213
|
}>): readonly VeterinaryResearchStudyPartyInvitation[];
|
|
220
|
-
/** Decodes the
|
|
221
|
-
export declare function
|
|
222
|
-
export {};
|
|
214
|
+
/** Decodes the attached JSON:API Consent document after validating its claims-only boundary. */
|
|
215
|
+
export declare function decodeVeterinaryResearchStudyInvitationDocument(communication: VeterinaryResearchStudyPartyInvitation): VeterinaryResearchStudyInvitationDocument;
|
package/dist/research-study.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { normalizeResearchStudyAssociatedParties,
|
|
1
|
+
import { normalizeResearchStudyAssociatedParties, normalizeResearchStudyR5FlatClaimsResource, projectResearchStudyAssociatedPartyFlatClaimResources, projectResearchStudyR5FlatClaimsResource, } from 'vet-data-utils-ts/research-study';
|
|
2
2
|
import { VeterinaryCommunicationPresetFilters, VeterinaryResearchCommunicationTopics, } from 'vet-data-utils-ts/communication';
|
|
3
|
+
import { normalizeGroupR5FlatClaimsResource, normalizeGroupR5ResearchTeam, projectGroupPractitionerRoleMemberFlatClaimResources, projectGroupR5FlatClaimsResource, } from 'vet-data-utils-ts/group';
|
|
4
|
+
import { HttpMediaTypes } from 'gdc-common-utils-ts/constants/http';
|
|
3
5
|
import { HealthcareConsentPurposes } from 'gdc-common-utils-ts/constants/healthcare';
|
|
4
6
|
import { ResourceTypesFhirR5 } from 'gdc-common-utils-ts/constants/fhir-resource-types';
|
|
5
7
|
import { InteroperableContext } from 'gdc-common-utils-ts/constants/lifecycle';
|
|
6
8
|
import { ClaimConsent, ConsentDecisions, ConsentStatuses, } from 'gdc-common-utils-ts/models/consent-rule';
|
|
7
|
-
|
|
8
|
-
const FHIR_RESOURCE_TYPES_SYSTEM = 'http://hl7.org/fhir/fhir-types';
|
|
9
|
+
import { CommunicationClaim } from 'gdc-common-utils-ts/models/interoperable-claims/communication-claims';
|
|
9
10
|
export const VeterinaryResearchStudyPermissionActions = Object.freeze([
|
|
10
11
|
'create',
|
|
11
12
|
'read',
|
|
@@ -68,7 +69,7 @@ export function buildVeterinaryResearchStudyActiveConsentBatch(inputs) {
|
|
|
68
69
|
const data = inputs.map(buildVeterinaryResearchStudyActiveConsentEntry);
|
|
69
70
|
return deepFreeze({ data });
|
|
70
71
|
}
|
|
71
|
-
/** Builds one POST Consent entry accepted by the VET ResearchStudy manager. */
|
|
72
|
+
/** Builds one flat-claims POST Consent entry accepted by the VET ResearchStudy manager. */
|
|
72
73
|
export function buildVeterinaryResearchStudyActiveConsentEntry(input) {
|
|
73
74
|
if (input.status !== undefined && input.status !== ConsentStatuses.Active) {
|
|
74
75
|
throw new TypeError('veterinary_research_study_active_consent_status_invalid');
|
|
@@ -87,7 +88,6 @@ export function buildVeterinaryResearchStudyActiveConsentEntry(input) {
|
|
|
87
88
|
resource: {
|
|
88
89
|
resourceType: ResourceTypesFhirR5.Consent,
|
|
89
90
|
id: consentId,
|
|
90
|
-
status: ConsentStatuses.Active,
|
|
91
91
|
meta: {
|
|
92
92
|
claims: {
|
|
93
93
|
'@context': InteroperableContext.FhirApi,
|
|
@@ -108,48 +108,85 @@ export function buildVeterinaryResearchStudyActiveConsentEntry(input) {
|
|
|
108
108
|
export function buildVeterinaryResearchStudyBatch(entries) {
|
|
109
109
|
if (!Array.isArray(entries) || entries.length === 0)
|
|
110
110
|
throw new TypeError('veterinary_research_study_batch_empty');
|
|
111
|
-
return deepFreeze({ data: entries.map(
|
|
111
|
+
return deepFreeze({ data: entries.map(normalizeResearchStudyBatchEntry) });
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Builds the flat-claims Group POST used to create one enumerated ResearchStudy
|
|
115
|
+
* review team. Every member is added later as its own PractitionerRole claim
|
|
116
|
+
* PATCH. Group remains a description only and cannot carry access policy.
|
|
117
|
+
*
|
|
118
|
+
* @see https://hl7.org/fhir/R5/group.html
|
|
119
|
+
* @see https://hl7.org/fhir/R5/group-search.html
|
|
120
|
+
*/
|
|
121
|
+
export function buildVeterinaryResearchStudyGroupCreateEntry(input) {
|
|
122
|
+
const group = normalizedResearchTeamGroup(input.group);
|
|
123
|
+
if (group.member.length > 0)
|
|
124
|
+
throw new TypeError('group_members_require_patch_entries');
|
|
125
|
+
return deepFreeze({
|
|
126
|
+
type: 'Group-v5.0.0',
|
|
127
|
+
resource: projectGroupR5FlatClaimsResource(group),
|
|
128
|
+
request: { method: 'POST', url: 'Group' },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Builds one independently correlated PATCH per PractitionerRole member. A
|
|
133
|
+
* PATCH changes descriptive membership only; DCR, Consent and SMART remain
|
|
134
|
+
* separate authorization boundaries.
|
|
135
|
+
*/
|
|
136
|
+
export function buildVeterinaryResearchStudyGroupMemberPatchEntries(input) {
|
|
137
|
+
const id = boundedGroupId(input.groupId);
|
|
138
|
+
const resources = projectGroupPractitionerRoleMemberFlatClaimResources({ groupId: id, members: input.members });
|
|
139
|
+
return deepFreeze(resources.map(resource => ({
|
|
140
|
+
type: 'Group-v5.0.0',
|
|
141
|
+
resource,
|
|
142
|
+
request: { method: 'PATCH', url: `Group/${id}` },
|
|
143
|
+
})));
|
|
144
|
+
}
|
|
145
|
+
/** Wraps only validated Group POST/PATCH entries in one JSON:API document. */
|
|
146
|
+
export function buildVeterinaryResearchStudyGroupBatch(entries) {
|
|
147
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
148
|
+
throw new TypeError('veterinary_research_study_group_batch_empty');
|
|
149
|
+
}
|
|
150
|
+
return deepFreeze({ data: entries.map(normalizeResearchStudyGroupEntry) });
|
|
112
151
|
}
|
|
113
152
|
export function buildVeterinaryResearchStudyCreateEntry(input) {
|
|
114
153
|
const study = normalizedStudy(input.study);
|
|
115
154
|
return deepFreeze({
|
|
116
155
|
type: 'ResearchStudy-v5.0.0',
|
|
117
|
-
resource:
|
|
156
|
+
resource: projectResearchStudyR5FlatClaimsResource(study),
|
|
118
157
|
request: { method: 'POST', url: 'ResearchStudy' },
|
|
119
158
|
});
|
|
120
159
|
}
|
|
121
160
|
/**
|
|
122
161
|
* Builds one independent `_batch` PATCH entry per R5 associatedParty. Keeping
|
|
123
|
-
* each
|
|
124
|
-
* from being correlated with the wrong PractitionerRole.
|
|
162
|
+
* each party's claim map in its own entry prevents roles, periods and
|
|
163
|
+
* classifiers from being correlated with the wrong PractitionerRole. Native
|
|
164
|
+
* `associatedParty` JSON is not sent to persistence. These entries update
|
|
125
165
|
* study membership only; they do not grant CRUDS access.
|
|
126
166
|
*/
|
|
127
167
|
export function buildVeterinaryResearchStudyPartyPatchEntries(input) {
|
|
128
168
|
const id = boundedId(input.researchStudyId);
|
|
129
|
-
const
|
|
130
|
-
|
|
169
|
+
const resources = projectResearchStudyAssociatedPartyFlatClaimResources({
|
|
170
|
+
researchStudyId: id,
|
|
171
|
+
associatedParties: input.associatedParties,
|
|
172
|
+
});
|
|
173
|
+
return deepFreeze(resources.map(resource => ({
|
|
131
174
|
type: 'ResearchStudy-v5.0.0',
|
|
132
|
-
resource
|
|
133
|
-
resourceType: 'ResearchStudy',
|
|
134
|
-
id,
|
|
135
|
-
associatedParty: [party],
|
|
136
|
-
}),
|
|
175
|
+
resource,
|
|
137
176
|
request: { method: 'PATCH', url: `ResearchStudy/${id}` },
|
|
138
177
|
})));
|
|
139
178
|
}
|
|
140
179
|
/**
|
|
141
180
|
* Builds the auditable R5 invitation sent to one ResearchStudy associated
|
|
142
|
-
* party. The Communication
|
|
143
|
-
*
|
|
181
|
+
* party. The claims-only Communication carries an `application/vnd.api+json`
|
|
182
|
+
* primary document with exactly one claims-only `Consent.status = draft`.
|
|
144
183
|
* Draft is descriptive only and can never satisfy authorization. The invitee
|
|
145
184
|
* must finish DCR and a controller must later persist a separate active
|
|
146
185
|
* Consent before the study-scoped SMART request can succeed.
|
|
147
186
|
*
|
|
148
|
-
*
|
|
149
|
-
* `
|
|
150
|
-
*
|
|
151
|
-
* search; their FHIR representation uses the matching REST interaction codes,
|
|
152
|
-
* where search is `search-type`. Delete is deliberately absent.
|
|
187
|
+
* The flat Consent claims retain the exact study boundary and affected
|
|
188
|
+
* `ResearchSubject` resource family. Delete is deliberately absent. Native
|
|
189
|
+
* FHIR projection belongs only at an explicit import/export boundary.
|
|
153
190
|
*
|
|
154
191
|
* @see https://hl7.org/fhir/R5/communication.html
|
|
155
192
|
* @see https://hl7.org/fhir/R5/consent.html
|
|
@@ -177,64 +214,46 @@ export function buildVeterinaryResearchStudyPartyInvitation(input) {
|
|
|
177
214
|
const consent = {
|
|
178
215
|
resourceType: 'Consent',
|
|
179
216
|
id: consentId,
|
|
180
|
-
status: 'draft',
|
|
181
|
-
grantor: [{ reference: controllerReference, type: 'Organization' }],
|
|
182
|
-
controller: [{ reference: controllerReference, type: 'Organization' }],
|
|
183
|
-
grantee: [{ reference: partyReference, type: 'PractitionerRole' }],
|
|
184
|
-
decision: 'permit',
|
|
185
|
-
provision: [{
|
|
186
|
-
actor: [{ reference: { reference: partyReference, type: 'PractitionerRole' } }],
|
|
187
|
-
action: VeterinaryResearchStudyPermissionActions.map(action => ({
|
|
188
|
-
coding: [{
|
|
189
|
-
system: FHIR_RESTFUL_INTERACTION_SYSTEM,
|
|
190
|
-
code: action === 'search' ? 'search-type' : action,
|
|
191
|
-
}],
|
|
192
|
-
})),
|
|
193
|
-
purpose: [{ system: topic.system, code: topic.code }],
|
|
194
|
-
resourceType: [{ system: FHIR_RESOURCE_TYPES_SYSTEM, code: 'ResearchSubject' }],
|
|
195
|
-
data: [{ meaning: 'related', reference: { reference: researchStudyReference, type: 'ResearchStudy' } }],
|
|
196
|
-
}],
|
|
197
217
|
meta: {
|
|
198
218
|
claims: {
|
|
199
|
-
[VeterinaryResearchStudyInvitationClaimNames.Context]:
|
|
200
|
-
[VeterinaryResearchStudyInvitationClaimNames.Status]:
|
|
201
|
-
[VeterinaryResearchStudyInvitationClaimNames.Decision]:
|
|
219
|
+
[VeterinaryResearchStudyInvitationClaimNames.Context]: InteroperableContext.FhirApi,
|
|
220
|
+
[VeterinaryResearchStudyInvitationClaimNames.Status]: ConsentStatuses.Draft,
|
|
221
|
+
[VeterinaryResearchStudyInvitationClaimNames.Decision]: ConsentDecisions.Permit,
|
|
202
222
|
[VeterinaryResearchStudyInvitationClaimNames.ActorIdentifier]: partyReference,
|
|
223
|
+
[ClaimConsent.grantee]: partyReference,
|
|
203
224
|
[VeterinaryResearchStudyInvitationClaimNames.Purpose]: topic.value,
|
|
204
225
|
[VeterinaryResearchStudyInvitationClaimNames.Action]: VeterinaryResearchStudyPermissionActions.join(','),
|
|
205
226
|
[VeterinaryResearchStudyInvitationClaimNames.ResourceType]: 'ResearchSubject',
|
|
227
|
+
[ClaimConsent.sourceReference]: researchStudyReference,
|
|
206
228
|
},
|
|
207
229
|
},
|
|
208
230
|
};
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
entry: [{
|
|
213
|
-
fullUrl: `urn:uuid:${consentId}`,
|
|
231
|
+
const document = {
|
|
232
|
+
data: [{
|
|
233
|
+
type: 'Consent-v5.0.0',
|
|
214
234
|
resource: consent,
|
|
215
235
|
request: { method: 'POST', url: 'Consent' },
|
|
216
236
|
}],
|
|
217
237
|
};
|
|
218
238
|
const category = VeterinaryCommunicationPresetFilters.ResearchAgreements.categories[0];
|
|
219
|
-
const [categorySystem, categoryCode] = splitCodingToken(category);
|
|
220
239
|
const sent = optionalInstant(input.sentAt);
|
|
221
240
|
return deepFreeze({
|
|
222
241
|
resourceType: 'Communication',
|
|
223
242
|
id: communicationId,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
243
|
+
meta: { claims: {
|
|
244
|
+
'@context': InteroperableContext.FhirApi,
|
|
245
|
+
[CommunicationClaim.Identifier]: communicationId,
|
|
246
|
+
[CommunicationClaim.Status]: 'completed',
|
|
247
|
+
[CommunicationClaim.Category]: category,
|
|
248
|
+
[CommunicationClaim.Topic]: topic.value,
|
|
249
|
+
[CommunicationClaim.ContentReference]: researchStudyReference,
|
|
250
|
+
[CommunicationClaim.Recipient]: partyReference,
|
|
251
|
+
[CommunicationClaim.Sender]: controllerReference,
|
|
252
|
+
...(sent ? { [CommunicationClaim.Sent]: sent } : {}),
|
|
253
|
+
[CommunicationClaim.ContentAttachmentType]: HttpMediaTypes.JsonApi,
|
|
254
|
+
[CommunicationClaim.ContentAttachmentTitle]: 'research-study-draft-consent.json',
|
|
255
|
+
[CommunicationClaim.ContentAttachmentData]: encodeJsonBase64(document),
|
|
256
|
+
} },
|
|
238
257
|
});
|
|
239
258
|
}
|
|
240
259
|
/**
|
|
@@ -253,41 +272,77 @@ export function buildVeterinaryResearchStudyPartyInvitations(input) {
|
|
|
253
272
|
...invitation,
|
|
254
273
|
})));
|
|
255
274
|
}
|
|
256
|
-
/** Decodes the
|
|
257
|
-
export function
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
275
|
+
/** Decodes the attached JSON:API Consent document after validating its claims-only boundary. */
|
|
276
|
+
export function decodeVeterinaryResearchStudyInvitationDocument(communication) {
|
|
277
|
+
if (!isClaimsOnlyResource(communication, 'Communication')) {
|
|
278
|
+
throw new TypeError('veterinary_research_study_invitation_document_invalid');
|
|
279
|
+
}
|
|
280
|
+
const claims = communication.meta.claims;
|
|
281
|
+
const encoded = claims[CommunicationClaim.ContentAttachmentData];
|
|
282
|
+
if (claims[CommunicationClaim.ContentAttachmentType] !== HttpMediaTypes.JsonApi || !encoded) {
|
|
283
|
+
throw new TypeError('veterinary_research_study_invitation_document_invalid');
|
|
263
284
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
285
|
+
let decoded;
|
|
286
|
+
try {
|
|
287
|
+
decoded = JSON.parse(decodeJsonBase64(encoded));
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
throw new TypeError('veterinary_research_study_invitation_document_invalid');
|
|
291
|
+
}
|
|
292
|
+
const entry = decoded?.data?.[0];
|
|
293
|
+
if (decoded?.data?.length !== 1
|
|
294
|
+
|| entry?.type !== 'Consent-v5.0.0'
|
|
295
|
+
|| entry.request?.method !== 'POST'
|
|
296
|
+
|| entry.request.url !== 'Consent'
|
|
297
|
+
|| !isClaimsOnlyResource(entry.resource, 'Consent')
|
|
298
|
+
|| entry.resource.meta.claims[ClaimConsent.status] !== ConsentStatuses.Draft) {
|
|
299
|
+
throw new TypeError('veterinary_research_study_invitation_document_invalid');
|
|
271
300
|
}
|
|
272
301
|
return deepFreeze(decoded);
|
|
273
302
|
}
|
|
274
303
|
function normalizedStudy(input) {
|
|
275
304
|
if (!input || input.resourceType !== 'ResearchStudy')
|
|
276
305
|
throw new TypeError('veterinary_research_study_invalid');
|
|
306
|
+
if (input.associatedParty !== undefined) {
|
|
307
|
+
throw new TypeError('veterinary_research_study_parties_require_patch_entries');
|
|
308
|
+
}
|
|
277
309
|
const source = clonePlain(input);
|
|
278
310
|
const id = boundedId(input.id);
|
|
279
311
|
const status = String(input.status || '');
|
|
280
312
|
if (!['draft', 'active', 'retired', 'unknown'].includes(status)) {
|
|
281
313
|
throw new TypeError('veterinary_research_study_status_invalid');
|
|
282
314
|
}
|
|
283
|
-
const associatedParty = input.associatedParty === undefined
|
|
284
|
-
? undefined
|
|
285
|
-
: normalizeResearchStudyAssociatedParties(input.associatedParty);
|
|
286
315
|
return deepFreeze({
|
|
287
316
|
...source,
|
|
288
317
|
id,
|
|
289
318
|
status: status,
|
|
290
|
-
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
function normalizedResearchTeamGroup(input) {
|
|
322
|
+
const source = clonePlain(input);
|
|
323
|
+
const normalized = normalizeGroupR5ResearchTeam(source);
|
|
324
|
+
return deepFreeze({ ...normalized, id: boundedGroupId(normalized.id) });
|
|
325
|
+
}
|
|
326
|
+
function normalizeResearchStudyGroupEntry(input) {
|
|
327
|
+
if (!input || input.type !== 'Group-v5.0.0') {
|
|
328
|
+
throw new TypeError('veterinary_research_study_group_entry_invalid');
|
|
329
|
+
}
|
|
330
|
+
let resource;
|
|
331
|
+
try {
|
|
332
|
+
resource = normalizeGroupR5FlatClaimsResource(input.resource);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
throw new TypeError('veterinary_research_study_group_entry_invalid');
|
|
336
|
+
}
|
|
337
|
+
const method = input.request?.method;
|
|
338
|
+
const expectedUrl = method === 'POST' ? 'Group' : method === 'PATCH' ? `Group/${resource.id}` : '';
|
|
339
|
+
if (!expectedUrl || input.request.url !== expectedUrl) {
|
|
340
|
+
throw new TypeError('veterinary_research_study_group_request_invalid');
|
|
341
|
+
}
|
|
342
|
+
return deepFreeze({
|
|
343
|
+
type: 'Group-v5.0.0',
|
|
344
|
+
resource,
|
|
345
|
+
request: { method, url: expectedUrl },
|
|
291
346
|
});
|
|
292
347
|
}
|
|
293
348
|
function clonePlain(value) {
|
|
@@ -298,33 +353,44 @@ function clonePlain(value) {
|
|
|
298
353
|
}
|
|
299
354
|
return value;
|
|
300
355
|
}
|
|
301
|
-
function
|
|
302
|
-
|
|
303
|
-
|
|
356
|
+
function normalizeResearchStudyBatchEntry(input) {
|
|
357
|
+
if (!input || input.type !== 'ResearchStudy-v5.0.0') {
|
|
358
|
+
throw new TypeError('veterinary_research_study_entry_invalid');
|
|
359
|
+
}
|
|
360
|
+
let resource;
|
|
361
|
+
try {
|
|
362
|
+
resource = normalizeResearchStudyR5FlatClaimsResource(input.resource);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
throw new TypeError('veterinary_research_study_entry_invalid');
|
|
366
|
+
}
|
|
367
|
+
const method = input.request?.method;
|
|
368
|
+
const expectedUrl = method === 'POST'
|
|
369
|
+
? 'ResearchStudy'
|
|
370
|
+
: method === 'PATCH'
|
|
371
|
+
? `ResearchStudy/${resource.id}`
|
|
372
|
+
: '';
|
|
373
|
+
if (!expectedUrl || input.request.url !== expectedUrl) {
|
|
374
|
+
throw new TypeError('veterinary_research_study_request_invalid');
|
|
375
|
+
}
|
|
304
376
|
return deepFreeze({
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
claims: { ...standardClaims, ...partyClaims },
|
|
309
|
-
},
|
|
377
|
+
type: 'ResearchStudy-v5.0.0',
|
|
378
|
+
resource,
|
|
379
|
+
request: { method, url: expectedUrl },
|
|
310
380
|
});
|
|
311
381
|
}
|
|
312
|
-
function aggregatePartyClaims(parties) {
|
|
313
|
-
const output = {};
|
|
314
|
-
for (const projection of projectResearchStudyAssociatedPartyClaims(parties)) {
|
|
315
|
-
for (const [name, values] of Object.entries(projection.claims)) {
|
|
316
|
-
if (values.length > 0)
|
|
317
|
-
(output[name] || (output[name] = [])).push(...values);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return Object.fromEntries(Object.entries(output).map(([name, values]) => [name, Object.freeze([...values])]));
|
|
321
|
-
}
|
|
322
382
|
function boundedId(value) {
|
|
323
383
|
const id = String(value || '').trim();
|
|
324
384
|
if (!/^[A-Za-z0-9\-.]{1,64}$/.test(id))
|
|
325
385
|
throw new TypeError('veterinary_research_study_id_invalid');
|
|
326
386
|
return id;
|
|
327
387
|
}
|
|
388
|
+
function boundedGroupId(value) {
|
|
389
|
+
const id = String(value || '').trim();
|
|
390
|
+
if (!/^[A-Za-z0-9\-.]{1,64}$/.test(id))
|
|
391
|
+
throw new TypeError('veterinary_research_study_group_id_invalid');
|
|
392
|
+
return id;
|
|
393
|
+
}
|
|
328
394
|
function boundedReference(value, resourceType) {
|
|
329
395
|
const reference = String(value || '').trim();
|
|
330
396
|
const prefix = `${resourceType}/`;
|
|
@@ -352,13 +418,6 @@ function optionalInstant(value) {
|
|
|
352
418
|
throw new TypeError('veterinary_research_study_invitation_sent_invalid');
|
|
353
419
|
return instant;
|
|
354
420
|
}
|
|
355
|
-
function splitCodingToken(value) {
|
|
356
|
-
const separator = value.lastIndexOf('|');
|
|
357
|
-
if (separator < 1 || separator === value.length - 1) {
|
|
358
|
-
throw new TypeError('veterinary_research_study_invitation_category_invalid');
|
|
359
|
-
}
|
|
360
|
-
return [value.slice(0, separator), value.slice(separator + 1)];
|
|
361
|
-
}
|
|
362
421
|
function encodeJsonBase64(value) {
|
|
363
422
|
const bytes = new TextEncoder().encode(JSON.stringify(value));
|
|
364
423
|
let binary = '';
|
|
@@ -379,3 +438,15 @@ function deepFreeze(value, seen = new WeakSet()) {
|
|
|
379
438
|
deepFreeze(nested, seen);
|
|
380
439
|
return Object.freeze(value);
|
|
381
440
|
}
|
|
441
|
+
function isRecord(value) {
|
|
442
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
443
|
+
}
|
|
444
|
+
function isClaimsOnlyResource(value, resourceType) {
|
|
445
|
+
if (!isRecord(value) || value.resourceType !== resourceType)
|
|
446
|
+
return false;
|
|
447
|
+
if (Object.keys(value).some(key => !['resourceType', 'id', 'meta'].includes(key)))
|
|
448
|
+
return false;
|
|
449
|
+
if (typeof value.id !== 'string' || !isRecord(value.meta) || !isRecord(value.meta.claims))
|
|
450
|
+
return false;
|
|
451
|
+
return Object.values(value.meta.claims).every(claim => typeof claim === 'string' && claim.trim().length > 0);
|
|
452
|
+
}
|
|
@@ -4,8 +4,9 @@ This SDK separates study membership, an invitation, and authorization. They
|
|
|
4
4
|
are three different facts:
|
|
5
5
|
|
|
6
6
|
1. `ResearchStudy.associatedParty` records who is expected to collaborate.
|
|
7
|
-
2. One
|
|
8
|
-
|
|
7
|
+
2. One claims-only `Communication` invites that exact `PractitionerRole` and
|
|
8
|
+
carries a JSON:API primary document with one claims-only
|
|
9
|
+
`Consent.status = draft`.
|
|
9
10
|
3. Only after the professional completes DCR and the controller persists a
|
|
10
11
|
later `Consent.status = active` may the professional request the
|
|
11
12
|
study-scoped SMART authorization.
|
|
@@ -17,10 +18,30 @@ related to the exact ResearchStudy. Whole-twin delete is absent.
|
|
|
17
18
|
```ts
|
|
18
19
|
import {
|
|
19
20
|
buildVeterinaryResearchStudyActiveConsentBatch,
|
|
21
|
+
buildVeterinaryResearchStudyGroupBatch,
|
|
22
|
+
buildVeterinaryResearchStudyGroupCreateEntry,
|
|
23
|
+
buildVeterinaryResearchStudyGroupMemberPatchEntries,
|
|
20
24
|
buildVeterinaryResearchStudyPartyInvitations,
|
|
21
25
|
buildVeterinaryResearchStudySmartAuthorization,
|
|
22
26
|
} from 'vet-sdk-core-ts/research-study'
|
|
23
27
|
|
|
28
|
+
const researchGroupId = 'review-team'
|
|
29
|
+
const groupCreate = buildVeterinaryResearchStudyGroupCreateEntry({
|
|
30
|
+
group: {
|
|
31
|
+
resourceType: 'Group',
|
|
32
|
+
id: researchGroupId,
|
|
33
|
+
type: 'practitioner',
|
|
34
|
+
membership: 'enumerated',
|
|
35
|
+
member: [],
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
const memberPatches = buildVeterinaryResearchStudyGroupMemberPatchEntries({
|
|
39
|
+
groupId: researchGroupId,
|
|
40
|
+
members: associatedParties.map(({ party }) => ({ entity: party })),
|
|
41
|
+
})
|
|
42
|
+
const groupBatch = buildVeterinaryResearchStudyGroupBatch([groupCreate, ...memberPatches])
|
|
43
|
+
// The protected controller BFF submits `groupBatch`; membership grants no access.
|
|
44
|
+
|
|
24
45
|
const invitations = buildVeterinaryResearchStudyPartyInvitations({
|
|
25
46
|
researchStudyId: study.id,
|
|
26
47
|
controllerReference: organizationReference,
|
|
@@ -60,23 +81,48 @@ const authorization = buildVeterinaryResearchStudySmartAuthorization({
|
|
|
60
81
|
await professionalRuntime.requestSmartToken(authorization)
|
|
61
82
|
```
|
|
62
83
|
|
|
84
|
+
The controller may independently create a descriptive review team with
|
|
85
|
+
`buildVeterinaryResearchStudyGroupCreateEntry()` and append each registered
|
|
86
|
+
`PractitionerRole` with
|
|
87
|
+
`buildVeterinaryResearchStudyGroupMemberPatchEntries()`. Individual
|
|
88
|
+
professionals are represented in the study by their separate
|
|
89
|
+
`ResearchStudy.associatedParty` PATCH entries. The professional Group is not
|
|
90
|
+
`ResearchStudy.recruitment.eligibility`: that FHIR element describes the
|
|
91
|
+
research-subject cohort or its eligibility criteria. Group membership is not
|
|
92
|
+
evidence of DCR or Consent and never grants the SMART authorization above.
|
|
93
|
+
|
|
94
|
+
GW persistence entries for ResearchStudy, professional Group and active
|
|
95
|
+
Consent contain only `resourceType`, `id` and `resource.meta.claims`. Their
|
|
96
|
+
FHIR-like field names are flat claims. Native nested FHIR JSON is reserved for
|
|
97
|
+
an explicit export/projection boundary, not these GW builders.
|
|
98
|
+
|
|
99
|
+
The draft Consent inside the Communication attachment is an explicit
|
|
100
|
+
`application/vnd.api+json` proposal for recipient review. Both the outer
|
|
101
|
+
Communication and the attached Consent contain only `resourceType`, `id` and
|
|
102
|
+
`resource.meta.claims`. It never authorizes access; only the later flat-claims
|
|
103
|
+
active Consent POST is evaluated by GW.
|
|
104
|
+
|
|
105
|
+
These builders follow the native [FHIR R5 Group resource](https://hl7.org/fhir/R5/group.html)
|
|
106
|
+
and its exact [resource-specific search catalogue](https://hl7.org/fhir/R5/group-search.html).
|
|
107
|
+
|
|
63
108
|
The SMART authorization intentionally does not carry `subjectDid`:
|
|
64
109
|
`openProfessional()` supplies its DCR-bound actor as `sub`, while GW derives
|
|
65
110
|
the authorization subject `ResearchStudy/<id>` from the exact unescaped scope.
|
|
66
111
|
Percent-encoding the `/` in that scope would no longer match the active
|
|
67
112
|
Consent action and is rejected.
|
|
68
113
|
|
|
69
|
-
The
|
|
70
|
-
`Communication.
|
|
71
|
-
`
|
|
114
|
+
The JSON:API primary document is base64 encoded in canonical
|
|
115
|
+
`Communication.content-attachment-data`, paired with
|
|
116
|
+
`Communication.content-attachment-type = application/vnd.api+json`. The
|
|
117
|
+
Consent claims use:
|
|
72
118
|
|
|
73
|
-
- `Consent.grantee` and `Consent.
|
|
119
|
+
- `Consent.grantee` and `Consent.actor-identifier` for the invited
|
|
74
120
|
`PractitionerRole`;
|
|
75
|
-
-
|
|
76
|
-
- `Consent.
|
|
77
|
-
- `Consent.
|
|
78
|
-
- `Consent.
|
|
79
|
-
-
|
|
121
|
+
- outer `Communication.sender` for the controller organization;
|
|
122
|
+
- `Consent.purpose` with HL7 v3 ActReason `HRESCH`;
|
|
123
|
+
- `Consent.resource-type` for `ResearchSubject`;
|
|
124
|
+
- `Consent.source-reference` for the exact `ResearchStudy/{id}`;
|
|
125
|
+
- `Consent.action` for `create`, `read`, `update`, and `search`.
|
|
80
126
|
|
|
81
127
|
FHIR R5 references:
|
|
82
128
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vet-sdk-core-ts",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.14",
|
|
4
4
|
"description": "Browser-safe VetChain core contracts and governed animal species identifiers",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -64,6 +64,6 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@noble/hashes": "^2.2.0",
|
|
66
66
|
"gdc-common-utils-ts": "2.9.4",
|
|
67
|
-
"vet-data-utils-ts": "0.5.
|
|
67
|
+
"vet-data-utils-ts": "0.5.2"
|
|
68
68
|
}
|
|
69
69
|
}
|