gdc-common-utils-ts 2.3.5 → 2.3.6
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.
|
@@ -210,4 +210,115 @@ function buildConsentDecisionScenario(params) {
|
|
|
210
210
|
* consent state + actor target + role + purpose + requested scope.
|
|
211
211
|
*/
|
|
212
212
|
export const EXAMPLE_PROFESSIONAL_CONSENT_SCENARIOS = Object.freeze({
|
|
213
|
-
|
|
213
|
+
physicianByEmailContinuousCareAllergiesAllowed: buildConsentDecisionScenario({
|
|
214
|
+
actorId: EXAMPLE_PHYSICIAN_EMAIL,
|
|
215
|
+
actorRole: HealthcareActorRoles.Physician,
|
|
216
|
+
purpose: HealthcareConsentPurposes.Treatment,
|
|
217
|
+
consentActions: [HealthcareBasicSections.AllergiesAndIntolerances.claim],
|
|
218
|
+
requestedSections: HealthcareBasicSections.AllergiesAndIntolerances.claim,
|
|
219
|
+
includedTypes: [
|
|
220
|
+
ResourceTypesFhirR4.Composition,
|
|
221
|
+
ResourceTypesFhirR4.AllergyIntolerance,
|
|
222
|
+
ResourceTypesFhirR4.DocumentReference,
|
|
223
|
+
],
|
|
224
|
+
expectedSmartTokenDecision: 'allowed',
|
|
225
|
+
reason: 'physician is targeted directly by email and role for continuous care over allergies section',
|
|
226
|
+
}),
|
|
227
|
+
physicianByEmailEmergencySummaryAllowed: buildConsentDecisionScenario({
|
|
228
|
+
actorId: EXAMPLE_PHYSICIAN_EMAIL,
|
|
229
|
+
actorRole: HealthcareActorRoles.Physician,
|
|
230
|
+
purpose: HealthcareConsentPurposes.EmergencyTreatment,
|
|
231
|
+
consentActions: [HealthcareBasicSections.PatientSummaryDocument.claim],
|
|
232
|
+
requestedSections: HealthcareBasicSections.PatientSummaryDocument.claim,
|
|
233
|
+
includedTypes: [
|
|
234
|
+
ResourceTypesFhirR4.Composition,
|
|
235
|
+
ResourceTypesFhirR4.DocumentReference,
|
|
236
|
+
ResourceTypesFhirR4.Observation,
|
|
237
|
+
],
|
|
238
|
+
expectedSmartTokenDecision: 'allowed',
|
|
239
|
+
reason: 'physician is targeted directly by email and role for emergency summary access',
|
|
240
|
+
}),
|
|
241
|
+
physicianByOrganizationResultsAllowed: buildConsentDecisionScenario({
|
|
242
|
+
actorId: { organizationUrl: EXAMPLE_PROVIDER_ORG_URL },
|
|
243
|
+
actorRole: HealthcareActorRoles.Physician,
|
|
244
|
+
purpose: HealthcareConsentPurposes.Treatment,
|
|
245
|
+
consentActions: [HealthcareBasicSections.Results.claim],
|
|
246
|
+
requestedSections: HealthcareBasicSections.Results.claim,
|
|
247
|
+
includedTypes: [
|
|
248
|
+
ResourceTypesFhirR4.Composition,
|
|
249
|
+
ResourceTypesFhirR4.DiagnosticReport,
|
|
250
|
+
ResourceTypesFhirR4.DocumentReference,
|
|
251
|
+
],
|
|
252
|
+
expectedSmartTokenDecision: 'allowed',
|
|
253
|
+
reason: 'consent is granted to a physician role within a given organization for continuous care results access',
|
|
254
|
+
}),
|
|
255
|
+
physicianByJurisdictionEmergencySummaryAllowed: buildConsentDecisionScenario({
|
|
256
|
+
actorId: EXAMPLE_JURISDICTION,
|
|
257
|
+
actorRole: HealthcareActorRoles.Physician,
|
|
258
|
+
purpose: HealthcareConsentPurposes.EmergencyTreatment,
|
|
259
|
+
consentActions: [HealthcareBasicSections.PatientSummaryDocument.claim],
|
|
260
|
+
requestedSections: HealthcareBasicSections.PatientSummaryDocument.claim,
|
|
261
|
+
includedTypes: [
|
|
262
|
+
ResourceTypesFhirR4.Composition,
|
|
263
|
+
ResourceTypesFhirR4.DocumentReference,
|
|
264
|
+
ResourceTypesFhirR4.Observation,
|
|
265
|
+
],
|
|
266
|
+
expectedSmartTokenDecision: 'allowed',
|
|
267
|
+
reason: 'consent is granted at jurisdiction level for physician emergency access',
|
|
268
|
+
}),
|
|
269
|
+
nursingByOrganizationMedicationHistoryAllowed: buildConsentDecisionScenario({
|
|
270
|
+
actorId: { organizationUrl: EXAMPLE_PROVIDER_ORG_URL },
|
|
271
|
+
actorRole: HealthcareActorRoles.NursingProfessional,
|
|
272
|
+
purpose: HealthcareConsentPurposes.Treatment,
|
|
273
|
+
consentActions: [HealthcareBasicSections.HistoryOfMedicationUse.claim],
|
|
274
|
+
requestedSections: HealthcareBasicSections.HistoryOfMedicationUse.claim,
|
|
275
|
+
includedTypes: [
|
|
276
|
+
ResourceTypesFhirR4.Composition,
|
|
277
|
+
ResourceTypesFhirR4.MedicationStatement,
|
|
278
|
+
ResourceTypesFhirR4.DocumentReference,
|
|
279
|
+
],
|
|
280
|
+
expectedSmartTokenDecision: 'allowed',
|
|
281
|
+
reason: 'nursing professional is allowed to read medication history for treatment through organization-scoped consent',
|
|
282
|
+
}),
|
|
283
|
+
paramedicByJurisdictionEmergencySummaryAllowed: buildConsentDecisionScenario({
|
|
284
|
+
actorId: EXAMPLE_JURISDICTION,
|
|
285
|
+
actorRole: HealthcareActorRoles.Paramedic,
|
|
286
|
+
purpose: HealthcareConsentPurposes.EmergencyTreatment,
|
|
287
|
+
consentActions: [HealthcareBasicSections.PatientSummaryDocument.claim],
|
|
288
|
+
requestedSections: HealthcareBasicSections.PatientSummaryDocument.claim,
|
|
289
|
+
includedTypes: [
|
|
290
|
+
ResourceTypesFhirR4.Composition,
|
|
291
|
+
ResourceTypesFhirR4.DocumentReference,
|
|
292
|
+
ResourceTypesFhirR4.Observation,
|
|
293
|
+
],
|
|
294
|
+
expectedSmartTokenDecision: 'allowed',
|
|
295
|
+
reason: 'paramedic receives emergency-only jurisdiction-scoped access to patient summary',
|
|
296
|
+
}),
|
|
297
|
+
physicianObstetricianDeniedWhenOnlyAllergiesConsent: buildConsentDecisionScenario({
|
|
298
|
+
actorId: EXAMPLE_PHYSICIAN_EMAIL,
|
|
299
|
+
actorRole: `${HealthcareActorRoles.Physician}:obstetrician`,
|
|
300
|
+
purpose: HealthcareConsentPurposes.Treatment,
|
|
301
|
+
consentActions: [HealthcareBasicSections.AllergiesAndIntolerances.claim],
|
|
302
|
+
requestedSections: HealthcareBasicSections.Results.claim,
|
|
303
|
+
includedTypes: [
|
|
304
|
+
ResourceTypesFhirR4.Composition,
|
|
305
|
+
ResourceTypesFhirR4.DiagnosticReport,
|
|
306
|
+
ResourceTypesFhirR4.DocumentReference,
|
|
307
|
+
],
|
|
308
|
+
expectedSmartTokenDecision: 'denied',
|
|
309
|
+
reason: 'requested SMART scope targets results but active consent only covers allergies section',
|
|
310
|
+
}),
|
|
311
|
+
physicianByEmailDeniedWhenConsentRevokedAndNoOrgNorJurisdictionConsentIsActive: buildConsentDecisionScenario({
|
|
312
|
+
actorId: EXAMPLE_PHYSICIAN_EMAIL,
|
|
313
|
+
actorRole: HealthcareActorRoles.Physician,
|
|
314
|
+
purpose: HealthcareConsentPurposes.EmergencyTreatment,
|
|
315
|
+
consentActions: [HealthcareBasicSections.PatientSummaryDocument.claim],
|
|
316
|
+
requestedSections: HealthcareBasicSections.PatientSummaryDocument.claim,
|
|
317
|
+
includedTypes: [
|
|
318
|
+
ResourceTypesFhirR4.Composition,
|
|
319
|
+
ResourceTypesFhirR4.DocumentReference,
|
|
320
|
+
],
|
|
321
|
+
expectedSmartTokenDecision: 'denied',
|
|
322
|
+
reason: 'matching consent rule existed before but is no longer active after controller deactivation/revocation',
|
|
323
|
+
}),
|
|
324
|
+
});
|
package/dist/models/indexing.js
CHANGED
|
@@ -193,7 +193,7 @@ export function buildIndexParametersFromClaims(claims, allowedClaims) {
|
|
|
193
193
|
const parameter = {
|
|
194
194
|
name: claimKey,
|
|
195
195
|
value: value,
|
|
196
|
-
type:
|
|
196
|
+
type: inferParameterTypeFromClaimKey(claimKey, rawValue),
|
|
197
197
|
...(claimKey === ObservationClaim.ValueQuantityUnit && typeof rawValue === 'string'
|
|
198
198
|
? { unit: rawValue }
|
|
199
199
|
: {}),
|
|
@@ -136,7 +136,13 @@ export class BundleReader {
|
|
|
136
136
|
}
|
|
137
137
|
const entries = this.getEntries();
|
|
138
138
|
for (let index = 0; index < entries.length; index += 1) {
|
|
139
|
-
|
|
139
|
+
const resource = asRecord(entries[index].resource);
|
|
140
|
+
const resourceType = asNonEmptyString(resource.resourceType) || 'resource';
|
|
141
|
+
const candidates = new Set([
|
|
142
|
+
...this.resolveEntryReferenceCandidates(entries[index]),
|
|
143
|
+
`${resourceType}#${index}`,
|
|
144
|
+
]);
|
|
145
|
+
if (candidates.has(normalizedIdentifier)) {
|
|
140
146
|
return index;
|
|
141
147
|
}
|
|
142
148
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdc-common-utils-ts",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.6",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
"test": "jest",
|
|
20
20
|
"test:coverage": "jest --coverage",
|
|
21
21
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
22
|
-
"
|
|
22
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
23
|
+
"build": "npm run clean && tsc -p tsconfig.build.json && node patch-esm-imports.mjs",
|
|
24
|
+
"verify:dist": "node scripts/verify-dist-syntax.mjs",
|
|
25
|
+
"prepack": "npm run verify:dist",
|
|
23
26
|
"prepublishOnly": "npm run typecheck && npm test -- --watchman=false && npm run build"
|
|
24
27
|
},
|
|
25
28
|
"main": "./dist/index.js",
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { BundleResourceDateFilter, BundleResourceIdFilters } from './bundle-query';
|
|
2
|
-
/**
|
|
3
|
-
* Canonical high-level filter accepted by both Bundle section readers and
|
|
4
|
-
* `FhirDocumentFacade`.
|
|
5
|
-
*/
|
|
6
|
-
export type ClinicalResourceFilter = Readonly<{
|
|
7
|
-
sections?: string[];
|
|
8
|
-
types?: string[];
|
|
9
|
-
date?: BundleResourceDateFilter;
|
|
10
|
-
}>;
|
|
11
|
-
/**
|
|
12
|
-
* Chainable editor for one clinical document resource filter.
|
|
13
|
-
*
|
|
14
|
-
* The built value can be passed unchanged to:
|
|
15
|
-
*
|
|
16
|
-
* - `BundleReader.getDocumentSectionResourceIds(...)`
|
|
17
|
-
* - `BundleReader.getDocumentSectionResourceEntries(...)`
|
|
18
|
-
* - `FhirDocumentFacade.getResourcesByFilter(...)`
|
|
19
|
-
* - `FhirDocumentFacade.getResourceCount(...)`
|
|
20
|
-
*/
|
|
21
|
-
export declare class ClinicalResourceFilterEditor {
|
|
22
|
-
private sections?;
|
|
23
|
-
private types?;
|
|
24
|
-
private date?;
|
|
25
|
-
/** Replaces the selected Composition sections. An empty list means all. */
|
|
26
|
-
setSections(values: readonly string[]): this;
|
|
27
|
-
/** Replaces the selected FHIR resource types. An empty list means all. */
|
|
28
|
-
setTypes(values: readonly string[]): this;
|
|
29
|
-
/** Sets the complete clinical date interval. Either boundary may be omitted. */
|
|
30
|
-
setPeriod(start?: string, end?: string): this;
|
|
31
|
-
/** Sets or clears only the start boundary of the clinical date interval. */
|
|
32
|
-
setPeriodStart(start?: string): this;
|
|
33
|
-
/** Sets or clears only the end boundary of the clinical date interval. */
|
|
34
|
-
setPeriodEnd(end?: string): this;
|
|
35
|
-
/** Removes both clinical date boundaries. */
|
|
36
|
-
clearPeriod(): this;
|
|
37
|
-
/** Returns a detached canonical filter safe for either public reader. */
|
|
38
|
-
build(): ClinicalResourceFilter & BundleResourceIdFilters;
|
|
39
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
-
/**
|
|
3
|
-
* Chainable editor for one clinical document resource filter.
|
|
4
|
-
*
|
|
5
|
-
* The built value can be passed unchanged to:
|
|
6
|
-
*
|
|
7
|
-
* - `BundleReader.getDocumentSectionResourceIds(...)`
|
|
8
|
-
* - `BundleReader.getDocumentSectionResourceEntries(...)`
|
|
9
|
-
* - `FhirDocumentFacade.getResourcesByFilter(...)`
|
|
10
|
-
* - `FhirDocumentFacade.getResourceCount(...)`
|
|
11
|
-
*/
|
|
12
|
-
export class ClinicalResourceFilterEditor {
|
|
13
|
-
sections;
|
|
14
|
-
types;
|
|
15
|
-
date;
|
|
16
|
-
/** Replaces the selected Composition sections. An empty list means all. */
|
|
17
|
-
setSections(values) {
|
|
18
|
-
this.sections = normalizeTokens(values);
|
|
19
|
-
return this;
|
|
20
|
-
}
|
|
21
|
-
/** Replaces the selected FHIR resource types. An empty list means all. */
|
|
22
|
-
setTypes(values) {
|
|
23
|
-
this.types = normalizeTokens(values);
|
|
24
|
-
return this;
|
|
25
|
-
}
|
|
26
|
-
/** Sets the complete clinical date interval. Either boundary may be omitted. */
|
|
27
|
-
setPeriod(start, end) {
|
|
28
|
-
this.date = buildDateFilter(start, end);
|
|
29
|
-
return this;
|
|
30
|
-
}
|
|
31
|
-
/** Sets or clears only the start boundary of the clinical date interval. */
|
|
32
|
-
setPeriodStart(start) {
|
|
33
|
-
this.date = buildDateFilter(start, this.date?.end);
|
|
34
|
-
return this;
|
|
35
|
-
}
|
|
36
|
-
/** Sets or clears only the end boundary of the clinical date interval. */
|
|
37
|
-
setPeriodEnd(end) {
|
|
38
|
-
this.date = buildDateFilter(this.date?.start, end);
|
|
39
|
-
return this;
|
|
40
|
-
}
|
|
41
|
-
/** Removes both clinical date boundaries. */
|
|
42
|
-
clearPeriod() {
|
|
43
|
-
this.date = undefined;
|
|
44
|
-
return this;
|
|
45
|
-
}
|
|
46
|
-
/** Returns a detached canonical filter safe for either public reader. */
|
|
47
|
-
build() {
|
|
48
|
-
return {
|
|
49
|
-
...(this.sections?.length ? { sections: [...this.sections] } : {}),
|
|
50
|
-
...(this.types?.length ? { types: [...this.types] } : {}),
|
|
51
|
-
...(this.date ? { date: { ...this.date } } : {}),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function normalizeTokens(values) {
|
|
56
|
-
const normalized = [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))];
|
|
57
|
-
return normalized.length ? normalized : undefined;
|
|
58
|
-
}
|
|
59
|
-
function buildDateFilter(start, end) {
|
|
60
|
-
const normalizedStart = normalizeDate(start, 'start');
|
|
61
|
-
const normalizedEnd = normalizeDate(end, 'end');
|
|
62
|
-
if (normalizedStart && normalizedEnd) {
|
|
63
|
-
if (Date.parse(normalizedStart) > Date.parse(normalizedEnd)) {
|
|
64
|
-
throw new RangeError('Clinical resource filter period start must not be after end.');
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (!normalizedStart && !normalizedEnd) {
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
...(normalizedStart ? { start: normalizedStart } : {}),
|
|
72
|
-
...(normalizedEnd ? { end: normalizedEnd } : {}),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function normalizeDate(value, boundary) {
|
|
76
|
-
const normalized = String(value || '').trim();
|
|
77
|
-
if (!normalized) {
|
|
78
|
-
return undefined;
|
|
79
|
-
}
|
|
80
|
-
if (!Number.isFinite(Date.parse(normalized))) {
|
|
81
|
-
throw new TypeError(`Clinical resource filter period ${boundary} must be a valid date.`);
|
|
82
|
-
}
|
|
83
|
-
return normalized;
|
|
84
|
-
}
|