vet-data-utils-ts 0.5.1 → 0.5.3
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 +12 -4
- package/dist/group.d.ts +16 -0
- package/dist/group.js +59 -0
- package/dist/research-study.d.ts +18 -2
- package/dist/research-study.js +61 -12
- package/dist/sectors.d.ts +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,10 +10,11 @@ veterinary assistant intents.
|
|
|
10
10
|
|
|
11
11
|
FHIR R5 ResearchStudy screens consume the exact 25 resource-specific search
|
|
12
12
|
parameters and the separate associated-party projection from
|
|
13
|
-
`vet-data-utils-ts/research-study`.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
`vet-data-utils-ts/research-study`. GW persistence receives only
|
|
14
|
+
`resourceType`, `id` and flat `resource.meta.claims`; each party uses its own
|
|
15
|
+
`ResearchStudy.party-*` claim map so repeating roles, periods and classifiers
|
|
16
|
+
cannot be attributed to the wrong party. A party classifier describes an
|
|
17
|
+
organization category; it never represents Consent or CRUDS authorization. See
|
|
17
18
|
[`docs/research-study-r5-contract.md`](docs/research-study-r5-contract.md).
|
|
18
19
|
|
|
19
20
|
ResearchStudy professional teams use native FHIR R5 Group resources from
|
|
@@ -23,6 +24,13 @@ enumerated PractitionerRole member normalization. Group membership is
|
|
|
23
24
|
descriptive and never carries authorization, permission or scope. See
|
|
24
25
|
[`docs/group-r5-contract.md`](docs/group-r5-contract.md).
|
|
25
26
|
|
|
27
|
+
`projectResearchStudyR5FlatClaimsResource()` and
|
|
28
|
+
`projectGroupR5FlatClaimsResource()` create the minimal claims-first resource;
|
|
29
|
+
their repeating-party/member companions return one correlated resource per
|
|
30
|
+
future PATCH. The matching `normalize*FlatClaimsResource()` functions reject
|
|
31
|
+
nested FHIR persistence fields and unknown claims. Native FHIR JSON remains an
|
|
32
|
+
explicit import, projection or export boundary.
|
|
33
|
+
|
|
26
34
|
Reusable Communication screens receive immutable workflow presets from
|
|
27
35
|
`vet-data-utils-ts/communication`. The research-agreement screen is fixed to
|
|
28
36
|
FHIR `notification` plus HL7 v3 ActReason `HRESCH` and does not expose a topic
|
package/dist/group.d.ts
CHANGED
|
@@ -42,6 +42,13 @@ export declare const GroupFlatClaimCatalog: Readonly<{
|
|
|
42
42
|
}>;
|
|
43
43
|
export type GroupR5SearchClaim = (typeof standardSearchClaims)[number];
|
|
44
44
|
export type GroupR5SearchClaims = Readonly<Partial<Record<GroupR5SearchClaim, readonly string[]>>>;
|
|
45
|
+
export type GroupR5FlatClaimsResource = Readonly<{
|
|
46
|
+
resourceType: 'Group';
|
|
47
|
+
id: string;
|
|
48
|
+
meta: Readonly<{
|
|
49
|
+
claims: Readonly<Record<string, readonly string[]>>;
|
|
50
|
+
}>;
|
|
51
|
+
}>;
|
|
45
52
|
export type GroupR5Reference = Readonly<{
|
|
46
53
|
reference: string;
|
|
47
54
|
type?: string;
|
|
@@ -87,4 +94,13 @@ export declare function normalizeGroupR5ResearchTeam(input: unknown): GroupR5Res
|
|
|
87
94
|
* @see https://hl7.org/fhir/R5/group-search.html
|
|
88
95
|
*/
|
|
89
96
|
export declare function projectGroupR5SearchClaims(input: unknown): GroupR5SearchClaims;
|
|
97
|
+
/** Projects native-shaped Group input to the claims-only GW persistence shape. */
|
|
98
|
+
export declare function projectGroupR5FlatClaimsResource(input: unknown): GroupR5FlatClaimsResource;
|
|
99
|
+
/** Projects one correlated claims-only GW resource per PractitionerRole member. */
|
|
100
|
+
export declare function projectGroupPractitionerRoleMemberFlatClaimResources(input: Readonly<{
|
|
101
|
+
groupId: string;
|
|
102
|
+
members: unknown;
|
|
103
|
+
}>): readonly GroupR5FlatClaimsResource[];
|
|
104
|
+
/** Validates and freezes a claims-only Group GW resource. */
|
|
105
|
+
export declare function normalizeGroupR5FlatClaimsResource(input: unknown): GroupR5FlatClaimsResource;
|
|
90
106
|
export {};
|
package/dist/group.js
CHANGED
|
@@ -151,6 +151,59 @@ export function projectGroupR5SearchClaims(input) {
|
|
|
151
151
|
addClaim(claims, 'Group.value', characteristicValues);
|
|
152
152
|
return Object.freeze(claims);
|
|
153
153
|
}
|
|
154
|
+
/** Projects native-shaped Group input to the claims-only GW persistence shape. */
|
|
155
|
+
export function projectGroupR5FlatClaimsResource(input) {
|
|
156
|
+
if (!isRecord(input) || input.resourceType !== 'Group')
|
|
157
|
+
throw new TypeError('group_resource_invalid');
|
|
158
|
+
const id = fhirId(input.id, 'group_flat_resource_invalid');
|
|
159
|
+
if (Array.isArray(input.member) && input.member.length > 0)
|
|
160
|
+
throw new TypeError('group_members_require_separate_resources');
|
|
161
|
+
return normalizeGroupR5FlatClaimsResource({
|
|
162
|
+
resourceType: 'Group',
|
|
163
|
+
id,
|
|
164
|
+
meta: { claims: projectGroupR5SearchClaims(input) },
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/** Projects one correlated claims-only GW resource per PractitionerRole member. */
|
|
168
|
+
export function projectGroupPractitionerRoleMemberFlatClaimResources(input) {
|
|
169
|
+
const id = fhirId(input.groupId, 'group_flat_resource_invalid');
|
|
170
|
+
const members = normalizeGroupPractitionerRoleMembers(input.members);
|
|
171
|
+
return Object.freeze(members.map(member => normalizeGroupR5FlatClaimsResource({
|
|
172
|
+
resourceType: 'Group',
|
|
173
|
+
id,
|
|
174
|
+
meta: {
|
|
175
|
+
claims: projectGroupR5SearchClaims({
|
|
176
|
+
resourceType: 'Group',
|
|
177
|
+
id,
|
|
178
|
+
type: 'practitioner',
|
|
179
|
+
membership: 'enumerated',
|
|
180
|
+
member: [member],
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
})));
|
|
184
|
+
}
|
|
185
|
+
/** Validates and freezes a claims-only Group GW resource. */
|
|
186
|
+
export function normalizeGroupR5FlatClaimsResource(input) {
|
|
187
|
+
if (!isRecord(input)
|
|
188
|
+
|| input.resourceType !== 'Group'
|
|
189
|
+
|| Object.keys(input).some(key => !['resourceType', 'id', 'meta'].includes(key))) {
|
|
190
|
+
throw new TypeError('group_flat_resource_invalid');
|
|
191
|
+
}
|
|
192
|
+
const id = fhirId(input.id, 'group_flat_resource_invalid');
|
|
193
|
+
if (!isRecord(input.meta) || !isRecord(input.meta.claims))
|
|
194
|
+
throw new TypeError('group_flat_resource_invalid');
|
|
195
|
+
const claims = {};
|
|
196
|
+
for (const [name, rawValues] of Object.entries(input.meta.claims)) {
|
|
197
|
+
if (!GroupFlatClaimCatalog.all.includes(name)
|
|
198
|
+
|| !Array.isArray(rawValues)
|
|
199
|
+
|| rawValues.length === 0
|
|
200
|
+
|| rawValues.some(value => typeof value !== 'string' || !value.trim())) {
|
|
201
|
+
throw new TypeError('group_flat_claim_invalid');
|
|
202
|
+
}
|
|
203
|
+
claims[name] = Object.freeze(rawValues.map(value => value.trim()));
|
|
204
|
+
}
|
|
205
|
+
return Object.freeze({ resourceType: 'Group', id, meta: Object.freeze({ claims: Object.freeze(claims) }) });
|
|
206
|
+
}
|
|
154
207
|
function addClaim(claims, name, values) {
|
|
155
208
|
const normalized = [...new Set(values.flatMap(value => strings(value)))];
|
|
156
209
|
if (normalized.length > 0)
|
|
@@ -225,3 +278,9 @@ function isFhirDateTime(value) {
|
|
|
225
278
|
function isRecord(value) {
|
|
226
279
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
227
280
|
}
|
|
281
|
+
function fhirId(value, error) {
|
|
282
|
+
const id = optionalString(value);
|
|
283
|
+
if (!id || !/^[A-Za-z0-9.-]{1,64}$/.test(id))
|
|
284
|
+
throw new TypeError(error);
|
|
285
|
+
return id;
|
|
286
|
+
}
|
package/dist/research-study.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export type ResearchStudyR5SearchParameter = Readonly<{
|
|
|
42
42
|
/** Exact resource-specific SearchParameters published by FHIR R5 5.0.0. */
|
|
43
43
|
export declare const ResearchStudyR5SearchParameterCatalog: readonly ResearchStudyR5SearchParameter[];
|
|
44
44
|
declare const standardSearchClaims: readonly `ResearchStudy.${string}`[];
|
|
45
|
-
declare const associatedPartyClaims: readonly ["ResearchStudy.
|
|
45
|
+
declare const associatedPartyClaims: readonly ["ResearchStudy.party-name", "ResearchStudy.party-role", "ResearchStudy.party-period-start", "ResearchStudy.party-period-end", "ResearchStudy.party-classifier", "ResearchStudy.party"];
|
|
46
46
|
/**
|
|
47
47
|
* Governed flat claims for ResearchStudy.
|
|
48
48
|
*
|
|
@@ -53,7 +53,7 @@ declare const associatedPartyClaims: readonly ["ResearchStudy.associated-party-n
|
|
|
53
53
|
*/
|
|
54
54
|
export declare const ResearchStudyFlatClaimCatalog: Readonly<{
|
|
55
55
|
standardSearch: readonly `ResearchStudy.${string}`[];
|
|
56
|
-
associatedParty: readonly ["ResearchStudy.
|
|
56
|
+
associatedParty: readonly ["ResearchStudy.party-name", "ResearchStudy.party-role", "ResearchStudy.party-period-start", "ResearchStudy.party-period-end", "ResearchStudy.party-classifier", "ResearchStudy.party"];
|
|
57
57
|
all: readonly `ResearchStudy.${string}`[];
|
|
58
58
|
}>;
|
|
59
59
|
export type ResearchStudyCoding = Readonly<{
|
|
@@ -89,6 +89,13 @@ export type ResearchStudyAssociatedPartyClaimProjection = Readonly<{
|
|
|
89
89
|
}>;
|
|
90
90
|
export type ResearchStudyR5SearchClaim = (typeof standardSearchClaims)[number];
|
|
91
91
|
export type ResearchStudyR5SearchClaims = Readonly<Partial<Record<ResearchStudyR5SearchClaim, readonly string[]>>>;
|
|
92
|
+
export type ResearchStudyR5FlatClaimsResource = Readonly<{
|
|
93
|
+
resourceType: 'ResearchStudy';
|
|
94
|
+
id: string;
|
|
95
|
+
meta: Readonly<{
|
|
96
|
+
claims: Readonly<Record<string, readonly string[]>>;
|
|
97
|
+
}>;
|
|
98
|
+
}>;
|
|
92
99
|
/**
|
|
93
100
|
* Projects the official resource-specific R5 SearchParameters from one native
|
|
94
101
|
* ResearchStudy into flat `resource.meta.claims`. Composite values use FHIR's
|
|
@@ -110,4 +117,13 @@ export declare function normalizeResearchStudyAssociatedParties(input: unknown):
|
|
|
110
117
|
* required when a later Bundle PATCH changes one party.
|
|
111
118
|
*/
|
|
112
119
|
export declare function projectResearchStudyAssociatedPartyClaims(input: unknown): readonly ResearchStudyAssociatedPartyClaimProjection[];
|
|
120
|
+
/** Projects native-shaped study input to the claims-only GW persistence shape. */
|
|
121
|
+
export declare function projectResearchStudyR5FlatClaimsResource(input: unknown): ResearchStudyR5FlatClaimsResource;
|
|
122
|
+
/** Projects one correlated claims-only GW resource per associated party. */
|
|
123
|
+
export declare function projectResearchStudyAssociatedPartyFlatClaimResources(input: Readonly<{
|
|
124
|
+
researchStudyId: string;
|
|
125
|
+
associatedParties: unknown;
|
|
126
|
+
}>): readonly ResearchStudyR5FlatClaimsResource[];
|
|
127
|
+
/** Validates and freezes a claims-only ResearchStudy GW resource. */
|
|
128
|
+
export declare function normalizeResearchStudyR5FlatClaimsResource(input: unknown): ResearchStudyR5FlatClaimsResource;
|
|
113
129
|
export {};
|
package/dist/research-study.js
CHANGED
|
@@ -90,12 +90,12 @@ export const ResearchStudyR5SearchParameterCatalog = Object.freeze([
|
|
|
90
90
|
]);
|
|
91
91
|
const standardSearchClaims = Object.freeze(ResearchStudyR5SearchParameterCatalog.map(parameter => `ResearchStudy.${parameter.code}`));
|
|
92
92
|
const associatedPartyClaims = Object.freeze([
|
|
93
|
-
'ResearchStudy.
|
|
94
|
-
'ResearchStudy.
|
|
95
|
-
'ResearchStudy.
|
|
96
|
-
'ResearchStudy.
|
|
97
|
-
'ResearchStudy.
|
|
98
|
-
'ResearchStudy.
|
|
93
|
+
'ResearchStudy.party-name',
|
|
94
|
+
'ResearchStudy.party-role',
|
|
95
|
+
'ResearchStudy.party-period-start',
|
|
96
|
+
'ResearchStudy.party-period-end',
|
|
97
|
+
'ResearchStudy.party-classifier',
|
|
98
|
+
'ResearchStudy.party',
|
|
99
99
|
]);
|
|
100
100
|
/**
|
|
101
101
|
* Governed flat claims for ResearchStudy.
|
|
@@ -171,15 +171,58 @@ export function projectResearchStudyAssociatedPartyClaims(input) {
|
|
|
171
171
|
return Object.freeze(parties.map(party => Object.freeze({
|
|
172
172
|
associatedParty: party,
|
|
173
173
|
claims: Object.freeze({
|
|
174
|
-
'ResearchStudy.
|
|
175
|
-
'ResearchStudy.
|
|
176
|
-
'ResearchStudy.
|
|
177
|
-
'ResearchStudy.
|
|
178
|
-
'ResearchStudy.
|
|
179
|
-
'ResearchStudy.
|
|
174
|
+
'ResearchStudy.party-name': Object.freeze(party.name ? [party.name] : []),
|
|
175
|
+
'ResearchStudy.party-role': Object.freeze(conceptValues([party.role])),
|
|
176
|
+
'ResearchStudy.party-period-start': Object.freeze((party.period || []).flatMap(period => period.start ? [period.start] : [])),
|
|
177
|
+
'ResearchStudy.party-period-end': Object.freeze((party.period || []).flatMap(period => period.end ? [period.end] : [])),
|
|
178
|
+
'ResearchStudy.party-classifier': Object.freeze(conceptValues(party.classifier || [])),
|
|
179
|
+
'ResearchStudy.party': Object.freeze(party.party ? [party.party.reference] : []),
|
|
180
180
|
}),
|
|
181
181
|
})));
|
|
182
182
|
}
|
|
183
|
+
/** Projects native-shaped study input to the claims-only GW persistence shape. */
|
|
184
|
+
export function projectResearchStudyR5FlatClaimsResource(input) {
|
|
185
|
+
if (!isRecord(input) || input.resourceType !== 'ResearchStudy')
|
|
186
|
+
throw new TypeError('research_study_resource_invalid');
|
|
187
|
+
const id = fhirId(input.id, 'research_study_flat_resource_invalid');
|
|
188
|
+
if (input.associatedParty !== undefined)
|
|
189
|
+
throw new TypeError('research_study_parties_require_separate_resources');
|
|
190
|
+
return normalizeResearchStudyR5FlatClaimsResource({
|
|
191
|
+
resourceType: 'ResearchStudy',
|
|
192
|
+
id,
|
|
193
|
+
meta: { claims: projectResearchStudyR5SearchClaims(input) },
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/** Projects one correlated claims-only GW resource per associated party. */
|
|
197
|
+
export function projectResearchStudyAssociatedPartyFlatClaimResources(input) {
|
|
198
|
+
const id = fhirId(input.researchStudyId, 'research_study_flat_resource_invalid');
|
|
199
|
+
return Object.freeze(projectResearchStudyAssociatedPartyClaims(input.associatedParties).map(projection => {
|
|
200
|
+
const claims = Object.fromEntries(Object.entries(projection.claims).filter(([, values]) => values.length > 0));
|
|
201
|
+
return normalizeResearchStudyR5FlatClaimsResource({ resourceType: 'ResearchStudy', id, meta: { claims } });
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
/** Validates and freezes a claims-only ResearchStudy GW resource. */
|
|
205
|
+
export function normalizeResearchStudyR5FlatClaimsResource(input) {
|
|
206
|
+
if (!isRecord(input)
|
|
207
|
+
|| input.resourceType !== 'ResearchStudy'
|
|
208
|
+
|| Object.keys(input).some(key => !['resourceType', 'id', 'meta'].includes(key))) {
|
|
209
|
+
throw new TypeError('research_study_flat_resource_invalid');
|
|
210
|
+
}
|
|
211
|
+
const id = fhirId(input.id, 'research_study_flat_resource_invalid');
|
|
212
|
+
if (!isRecord(input.meta) || !isRecord(input.meta.claims))
|
|
213
|
+
throw new TypeError('research_study_flat_resource_invalid');
|
|
214
|
+
const claims = {};
|
|
215
|
+
for (const [name, rawValues] of Object.entries(input.meta.claims)) {
|
|
216
|
+
if (!ResearchStudyFlatClaimCatalog.all.includes(name)
|
|
217
|
+
|| !Array.isArray(rawValues)
|
|
218
|
+
|| rawValues.length === 0
|
|
219
|
+
|| rawValues.some(value => typeof value !== 'string' || !value.trim())) {
|
|
220
|
+
throw new TypeError('research_study_flat_claim_invalid');
|
|
221
|
+
}
|
|
222
|
+
claims[name] = Object.freeze(rawValues.map(value => value.trim()));
|
|
223
|
+
}
|
|
224
|
+
return Object.freeze({ resourceType: 'ResearchStudy', id, meta: Object.freeze({ claims: Object.freeze(claims) }) });
|
|
225
|
+
}
|
|
183
226
|
function normalizeAssociatedParty(value, index) {
|
|
184
227
|
if (!isRecord(value))
|
|
185
228
|
throw new TypeError(`research_study_associated_party_${index}_invalid`);
|
|
@@ -399,3 +442,9 @@ function isFhirDateTime(value) {
|
|
|
399
442
|
function isRecord(value) {
|
|
400
443
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
401
444
|
}
|
|
445
|
+
function fhirId(value, error) {
|
|
446
|
+
const id = optionalString(value);
|
|
447
|
+
if (!id || !/^[A-Za-z0-9.-]{1,64}$/.test(id))
|
|
448
|
+
throw new TypeError(error);
|
|
449
|
+
return id;
|
|
450
|
+
}
|
package/dist/sectors.d.ts
CHANGED
|
@@ -8,14 +8,17 @@
|
|
|
8
8
|
export declare const ExtendedDataspaceSectors: Readonly<{
|
|
9
9
|
readonly PublicSafety: "public-safety";
|
|
10
10
|
readonly HealthCare: "health-care";
|
|
11
|
+
readonly HealthLab: "health-lab";
|
|
11
12
|
readonly HealthResearch: "health-research";
|
|
12
13
|
readonly HealthTech: "health-tech";
|
|
13
14
|
readonly HealthInsurance: "health-insurance";
|
|
14
15
|
readonly AnimalCare: "animal-care";
|
|
16
|
+
readonly AnimalLab: "animal-lab";
|
|
15
17
|
readonly AnimalResearch: "animal-research";
|
|
16
18
|
readonly AnimalInsurance: "animal-insurance";
|
|
17
19
|
readonly AnimalTech: "animal-tech";
|
|
18
20
|
readonly OneHealthResearch: "onehealth-research";
|
|
21
|
+
readonly OneHealthLab: "onehealth-lab";
|
|
19
22
|
readonly OneHealthTech: "onehealth-tech";
|
|
20
23
|
}>;
|
|
21
24
|
export type ExtendedDataspaceSector = typeof ExtendedDataspaceSectors[keyof typeof ExtendedDataspaceSectors];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vet-data-utils-ts",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Browser-safe governed VetChain data contracts",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"typescript": "^5.5.4"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"gdc-common-utils-ts": "2.
|
|
87
|
+
"gdc-common-utils-ts": "2.9.4"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
90
|
"node": ">=20"
|