vet-data-utils-ts 0.5.26 → 0.5.27
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 +10 -0
- package/dist/health-dcat.d.ts +1 -0
- package/dist/health-dcat.js +8 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/provenance.d.ts +41 -0
- package/dist/provenance.js +99 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -119,6 +119,14 @@ cannot be attributed to the wrong party. A party classifier describes an
|
|
|
119
119
|
organization category; it never represents Consent or CRUDS authorization. See
|
|
120
120
|
[`docs/research-study-r5-contract.md`](docs/research-study-r5-contract.md).
|
|
121
121
|
|
|
122
|
+
Explicit study provenance uses a separate claims-first FHIR R5 `Provenance`
|
|
123
|
+
resource from `vet-data-utils-ts/provenance`. It exposes all thirteen
|
|
124
|
+
resource-specific standard SearchParameters and preserves agent correlation as
|
|
125
|
+
`Provenance.agent-<participation-type>-who`, where `<participation-type>` is an
|
|
126
|
+
exact code from the FHIR ParticipationRoleType ValueSet and the value is the
|
|
127
|
+
agent's FHIR Reference or URI. The standard `Provenance.agent` and
|
|
128
|
+
`Provenance.agent-type` claims are indexed alongside that correlation claim.
|
|
129
|
+
|
|
122
130
|
ResearchStudy professional teams use native FHIR R5 Group resources from
|
|
123
131
|
`vet-data-utils-ts/group`: required type and membership codes, the exact twelve
|
|
124
132
|
resource-specific SearchParameters, canonical flat claim projection and
|
|
@@ -221,6 +229,8 @@ controls whether external catalogue searches may return it. Archiving that
|
|
|
221
229
|
record withdraws discovery without deleting the study, eligibility/team
|
|
222
230
|
Groups, ResearchSubjects or Dataset, and without changing `dct:accessRights`.
|
|
223
231
|
No individual ResearchSubject identifier is projected into HealthDCAT.
|
|
232
|
+
The Dataset `dct:provenance` points to the separate FHIR `Provenance` record;
|
|
233
|
+
it does not replace the `prov:wasGeneratedBy` study relationship.
|
|
224
234
|
`Distribution` describes the HDAB-controlled representation; `DataService`
|
|
225
235
|
describes the host API serving Dataset metadata.
|
|
226
236
|
|
package/dist/health-dcat.d.ts
CHANGED
|
@@ -149,6 +149,7 @@ export declare function buildHealthDcatDataService(input: ControllerAuthority &
|
|
|
149
149
|
export declare function buildResearchStudyDatasetPublication(input: ControllerAuthority & Readonly<{
|
|
150
150
|
dataset: unknown;
|
|
151
151
|
researchStudyIdentifier: string;
|
|
152
|
+
provenanceReference: string;
|
|
152
153
|
catalogRecordIdentifier: string;
|
|
153
154
|
status: HealthDcatCatalogRecordStatus;
|
|
154
155
|
modifiedAt: string;
|
package/dist/health-dcat.js
CHANGED
|
@@ -303,12 +303,19 @@ export function buildResearchStudyDatasetPublication(input) {
|
|
|
303
303
|
const researchStudyIdentifier = required(input.researchStudyIdentifier, 'researchStudyIdentifier');
|
|
304
304
|
if (!researchStudyIdentifier.startsWith('ResearchStudy/'))
|
|
305
305
|
throw new TypeError('research_study_reference_invalid');
|
|
306
|
+
const provenanceReference = required(input.provenanceReference, 'provenanceReference');
|
|
307
|
+
if (!provenanceReference.startsWith('Provenance/'))
|
|
308
|
+
throw new TypeError('provenance_reference_invalid');
|
|
306
309
|
const modifiedAt = new Date(input.modifiedAt);
|
|
307
310
|
if (!Number.isFinite(modifiedAt.getTime()) || modifiedAt.toISOString() !== input.modifiedAt)
|
|
308
311
|
throw new TypeError('modifiedAt must be an ISO instant');
|
|
309
312
|
const linkedDataset = normalizeHealthDcatResource({
|
|
310
313
|
...dataset,
|
|
311
|
-
meta: { claims: {
|
|
314
|
+
meta: { claims: {
|
|
315
|
+
...dataset.meta.claims,
|
|
316
|
+
[ClaimsHealthDcatDataset.wasGeneratedBy]: researchStudyIdentifier,
|
|
317
|
+
[ClaimsHealthDcatDataset.provenance]: provenanceReference,
|
|
318
|
+
} },
|
|
312
319
|
});
|
|
313
320
|
const recordId = required(input.catalogRecordIdentifier, 'catalogRecordIdentifier');
|
|
314
321
|
const record = normalizeHealthDcatResource({
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from './iso-jurisdictions.js';
|
|
|
15
15
|
export * from './organization-application.js';
|
|
16
16
|
export * from './payment.js';
|
|
17
17
|
export * from './place-service-directory.js';
|
|
18
|
+
export * from './provenance.js';
|
|
18
19
|
export * from './research-study.js';
|
|
19
20
|
export * from './sectors.js';
|
|
20
21
|
export * from './shc.js';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from './iso-jurisdictions.js';
|
|
|
15
15
|
export * from './organization-application.js';
|
|
16
16
|
export * from './payment.js';
|
|
17
17
|
export * from './place-service-directory.js';
|
|
18
|
+
export * from './provenance.js';
|
|
18
19
|
export * from './research-study.js';
|
|
19
20
|
export * from './sectors.js';
|
|
20
21
|
export * from './shc.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Browser-safe FHIR R5 Provenance flat-claim vocabulary and builders. */
|
|
2
|
+
export declare const FhirR5ParticipationRoleTypeValueSet: "http://hl7.org/fhir/ValueSet/participation-role-type";
|
|
3
|
+
export declare const FhirR5ProvenanceParticipantTypeSystem: "http://terminology.hl7.org/CodeSystem/provenance-participant-type";
|
|
4
|
+
export declare const FhirR5ExtraSecurityRoleTypeSystem: "http://terminology.hl7.org/CodeSystem/extra-security-role-type";
|
|
5
|
+
export declare const DicomControlledTerminologySystem: "http://dicom.nema.org/resources/ontology/DCM";
|
|
6
|
+
/** Exact FHIR R5 ParticipationRoleType 5.0.0 expansion used by Provenance.agent.type. */
|
|
7
|
+
export declare const FhirR5ParticipationRoleTypeCodes: readonly ["enterer", "performer", "author", "verifier", "legal", "attester", "informant", "custodian", "assembler", "composer", "110150", "110151", "110152", "110153", "110154", "110155", "authserver", "datacollector", "dataprocessor", "datasubject", "humanuser"];
|
|
8
|
+
export type FhirR5ParticipationRoleTypeCode = (typeof FhirR5ParticipationRoleTypeCodes)[number];
|
|
9
|
+
export type ProvenanceSearchParameterType = 'date' | 'reference' | 'token';
|
|
10
|
+
export type ProvenanceR5SearchParameter = Readonly<{
|
|
11
|
+
code: string;
|
|
12
|
+
type: ProvenanceSearchParameterType;
|
|
13
|
+
expression: string;
|
|
14
|
+
}>;
|
|
15
|
+
/** Exact resource-specific FHIR R5 Provenance SearchParameters. */
|
|
16
|
+
export declare const ProvenanceR5SearchParameterCatalog: readonly ProvenanceR5SearchParameter[];
|
|
17
|
+
/** Standard search claims plus typed-agent correlation claims. */
|
|
18
|
+
export declare const ProvenanceFlatClaimCatalog: readonly string[];
|
|
19
|
+
export type ProvenanceR5FlatClaimsResource = Readonly<{
|
|
20
|
+
resourceType: 'Provenance';
|
|
21
|
+
id: string;
|
|
22
|
+
meta: Readonly<{
|
|
23
|
+
claims: Readonly<Record<string, readonly string[]>>;
|
|
24
|
+
}>;
|
|
25
|
+
}>;
|
|
26
|
+
/** Returns the correlated agent reference claim for one official participation type. */
|
|
27
|
+
export declare function provenanceAgentClaim(code: FhirR5ParticipationRoleTypeCode | string): string;
|
|
28
|
+
/** Returns the system-qualified FHIR token for one official participation type. */
|
|
29
|
+
export declare function participationRoleTypeToken(code: FhirR5ParticipationRoleTypeCode): string;
|
|
30
|
+
/** Validates a claims-only Provenance resource and rejects nested/native fields. */
|
|
31
|
+
export declare function normalizeProvenanceR5FlatClaimsResource(candidate: unknown): ProvenanceR5FlatClaimsResource;
|
|
32
|
+
/** Builds provenance for creation or update of a ResearchStudy with correlated typed agents. */
|
|
33
|
+
export declare function buildResearchStudyProvenanceResource(input: Readonly<{
|
|
34
|
+
id: string;
|
|
35
|
+
researchStudyReference: string;
|
|
36
|
+
recorded: string;
|
|
37
|
+
agents: readonly Readonly<{
|
|
38
|
+
type: FhirR5ParticipationRoleTypeCode;
|
|
39
|
+
who: string;
|
|
40
|
+
}>[];
|
|
41
|
+
}>): ProvenanceR5FlatClaimsResource;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/** Browser-safe FHIR R5 Provenance flat-claim vocabulary and builders. */
|
|
2
|
+
export const FhirR5ParticipationRoleTypeValueSet = 'http://hl7.org/fhir/ValueSet/participation-role-type';
|
|
3
|
+
export const FhirR5ProvenanceParticipantTypeSystem = 'http://terminology.hl7.org/CodeSystem/provenance-participant-type';
|
|
4
|
+
export const FhirR5ExtraSecurityRoleTypeSystem = 'http://terminology.hl7.org/CodeSystem/extra-security-role-type';
|
|
5
|
+
export const DicomControlledTerminologySystem = 'http://dicom.nema.org/resources/ontology/DCM';
|
|
6
|
+
/** Exact FHIR R5 ParticipationRoleType 5.0.0 expansion used by Provenance.agent.type. */
|
|
7
|
+
export const FhirR5ParticipationRoleTypeCodes = Object.freeze([
|
|
8
|
+
'enterer', 'performer', 'author', 'verifier', 'legal', 'attester', 'informant',
|
|
9
|
+
'custodian', 'assembler', 'composer', '110150', '110151', '110152', '110153',
|
|
10
|
+
'110154', '110155', 'authserver', 'datacollector', 'dataprocessor', 'datasubject',
|
|
11
|
+
'humanuser',
|
|
12
|
+
]);
|
|
13
|
+
/** Exact resource-specific FHIR R5 Provenance SearchParameters. */
|
|
14
|
+
export const ProvenanceR5SearchParameterCatalog = Object.freeze([
|
|
15
|
+
{ code: 'activity', type: 'token', expression: 'Provenance.activity' },
|
|
16
|
+
{ code: 'agent', type: 'reference', expression: 'Provenance.agent.who' },
|
|
17
|
+
{ code: 'agent-role', type: 'token', expression: 'Provenance.agent.role' },
|
|
18
|
+
{ code: 'agent-type', type: 'token', expression: 'Provenance.agent.type' },
|
|
19
|
+
{ code: 'based-on', type: 'reference', expression: 'Provenance.basedOn' },
|
|
20
|
+
{ code: 'encounter', type: 'reference', expression: 'Provenance.encounter' },
|
|
21
|
+
{ code: 'entity', type: 'reference', expression: 'Provenance.entity.what' },
|
|
22
|
+
{ code: 'location', type: 'reference', expression: 'Provenance.location' },
|
|
23
|
+
{ code: 'patient', type: 'reference', expression: 'Provenance.patient' },
|
|
24
|
+
{ code: 'recorded', type: 'date', expression: 'Provenance.recorded' },
|
|
25
|
+
{ code: 'signature-type', type: 'token', expression: 'Provenance.signature.type' },
|
|
26
|
+
{ code: 'target', type: 'reference', expression: 'Provenance.target' },
|
|
27
|
+
{ code: 'when', type: 'date', expression: 'Provenance.occurred.ofType(dateTime)' },
|
|
28
|
+
]);
|
|
29
|
+
const standardClaims = ProvenanceR5SearchParameterCatalog.map(parameter => `Provenance.${parameter.code}`);
|
|
30
|
+
const correlatedAgentClaims = FhirR5ParticipationRoleTypeCodes.map(code => `Provenance.agent-${code}-who`);
|
|
31
|
+
/** Standard search claims plus typed-agent correlation claims. */
|
|
32
|
+
export const ProvenanceFlatClaimCatalog = Object.freeze([...standardClaims, ...correlatedAgentClaims]);
|
|
33
|
+
/** Returns the correlated agent reference claim for one official participation type. */
|
|
34
|
+
export function provenanceAgentClaim(code) {
|
|
35
|
+
if (!FhirR5ParticipationRoleTypeCodes.includes(code))
|
|
36
|
+
throw new TypeError('participation_role_type_invalid');
|
|
37
|
+
return `Provenance.agent-${code}-who`;
|
|
38
|
+
}
|
|
39
|
+
/** Returns the system-qualified FHIR token for one official participation type. */
|
|
40
|
+
export function participationRoleTypeToken(code) {
|
|
41
|
+
const system = /^11015[0-5]$/.test(code)
|
|
42
|
+
? DicomControlledTerminologySystem
|
|
43
|
+
: ['authserver', 'datacollector', 'dataprocessor', 'datasubject', 'humanuser'].includes(code)
|
|
44
|
+
? FhirR5ExtraSecurityRoleTypeSystem
|
|
45
|
+
: FhirR5ProvenanceParticipantTypeSystem;
|
|
46
|
+
return `${system}|${code}`;
|
|
47
|
+
}
|
|
48
|
+
/** Validates a claims-only Provenance resource and rejects nested/native fields. */
|
|
49
|
+
export function normalizeProvenanceR5FlatClaimsResource(candidate) {
|
|
50
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
|
|
51
|
+
throw new TypeError('provenance_flat_resource_invalid');
|
|
52
|
+
const input = candidate;
|
|
53
|
+
if (input.resourceType !== 'Provenance' || typeof input.id !== 'string' || !input.id.trim())
|
|
54
|
+
throw new TypeError('provenance_flat_resource_invalid');
|
|
55
|
+
if (Object.keys(input).some(key => !['resourceType', 'id', 'meta'].includes(key)))
|
|
56
|
+
throw new TypeError('provenance_flat_resource_invalid');
|
|
57
|
+
const meta = input.meta;
|
|
58
|
+
if (!meta || typeof meta !== 'object' || Array.isArray(meta) || Object.keys(meta).some(key => key !== 'claims'))
|
|
59
|
+
throw new TypeError('provenance_flat_resource_invalid');
|
|
60
|
+
const claims = meta.claims;
|
|
61
|
+
if (!claims || typeof claims !== 'object' || Array.isArray(claims))
|
|
62
|
+
throw new TypeError('provenance_flat_resource_invalid');
|
|
63
|
+
const normalized = {};
|
|
64
|
+
for (const [name, rawValue] of Object.entries(claims)) {
|
|
65
|
+
if (!ProvenanceFlatClaimCatalog.includes(name))
|
|
66
|
+
throw new TypeError('provenance_flat_claim_invalid');
|
|
67
|
+
const values = (Array.isArray(rawValue) ? rawValue : [rawValue]).map(value => String(value).trim()).filter(Boolean);
|
|
68
|
+
if (!values.length)
|
|
69
|
+
throw new TypeError('provenance_flat_claim_value_invalid');
|
|
70
|
+
normalized[name] = Object.freeze(values);
|
|
71
|
+
}
|
|
72
|
+
return Object.freeze({ resourceType: 'Provenance', id: input.id.trim(), meta: Object.freeze({ claims: Object.freeze(normalized) }) });
|
|
73
|
+
}
|
|
74
|
+
/** Builds provenance for creation or update of a ResearchStudy with correlated typed agents. */
|
|
75
|
+
export function buildResearchStudyProvenanceResource(input) {
|
|
76
|
+
if (!input.researchStudyReference.startsWith('ResearchStudy/'))
|
|
77
|
+
throw new TypeError('research_study_reference_invalid');
|
|
78
|
+
const recorded = new Date(input.recorded);
|
|
79
|
+
if (!Number.isFinite(recorded.getTime()) || recorded.toISOString() !== input.recorded)
|
|
80
|
+
throw new TypeError('recorded_instant_invalid');
|
|
81
|
+
if (!input.agents.length)
|
|
82
|
+
throw new TypeError('provenance_agent_required');
|
|
83
|
+
const claims = {
|
|
84
|
+
'Provenance.target': [input.researchStudyReference],
|
|
85
|
+
'Provenance.recorded': [input.recorded],
|
|
86
|
+
'Provenance.agent': [],
|
|
87
|
+
'Provenance.agent-type': [],
|
|
88
|
+
};
|
|
89
|
+
for (const agent of input.agents) {
|
|
90
|
+
const who = String(agent.who || '').trim();
|
|
91
|
+
if (!who)
|
|
92
|
+
throw new TypeError('provenance_agent_who_required');
|
|
93
|
+
claims['Provenance.agent'].push(who);
|
|
94
|
+
claims['Provenance.agent-type'].push(participationRoleTypeToken(agent.type));
|
|
95
|
+
const correlatedClaim = provenanceAgentClaim(agent.type);
|
|
96
|
+
(claims[correlatedClaim] ||= []).push(who);
|
|
97
|
+
}
|
|
98
|
+
return normalizeProvenanceR5FlatClaimsResource({ resourceType: 'Provenance', id: input.id, meta: { claims } });
|
|
99
|
+
}
|
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.27",
|
|
4
4
|
"description": "Browser-safe governed VetChain data contracts",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -72,6 +72,10 @@
|
|
|
72
72
|
"types": "./dist/payment.d.ts",
|
|
73
73
|
"default": "./dist/payment.js"
|
|
74
74
|
},
|
|
75
|
+
"./provenance": {
|
|
76
|
+
"types": "./dist/provenance.d.ts",
|
|
77
|
+
"default": "./dist/provenance.js"
|
|
78
|
+
},
|
|
75
79
|
"./place-service-directory": {
|
|
76
80
|
"types": "./dist/place-service-directory.d.ts",
|
|
77
81
|
"default": "./dist/place-service-directory.js"
|