gdc-common-utils-ts 2.5.7 → 2.5.9
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/dist/claims/claims-helpers-medication-statement.js +3 -2
- package/dist/convert/convert-medication-statement.js +22 -10
- package/dist/examples/communication-attached-bundle-session.js +1 -1
- package/dist/examples/ips-bundle.js +1 -1
- package/dist/examples/shared.js +1 -1
- package/dist/models/interoperable-claims/medication-statement-claims.d.ts +63 -2
- package/dist/models/interoperable-claims/medication-statement-claims.js +58 -2
- package/dist/utils/clinical-resource-view.js +1 -1
- package/dist/utils/connected-device-fhir-r4.d.ts +79 -0
- package/dist/utils/connected-device-fhir-r4.js +211 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/medication-statement-entry-editor.d.ts +25 -5
- package/dist/utils/medication-statement-entry-editor.js +32 -9
- package/dist/utils/organization-test-network-credential.d.ts +9 -4
- package/dist/utils/organization-test-network-credential.js +3 -24
- package/package.json +1 -1
|
@@ -26,10 +26,11 @@ export function setMedicationEffective(claims, value) {
|
|
|
26
26
|
return setMedicationScalar(claims, MedicationStatementClaim.Effective, value);
|
|
27
27
|
}
|
|
28
28
|
export function getMedicationText(claims) {
|
|
29
|
-
return getMedicationScalar(claims, MedicationStatementClaim.
|
|
29
|
+
return getMedicationScalar(claims, MedicationStatementClaim.CodeText)
|
|
30
|
+
|| getMedicationScalar(claims, MedicationStatementClaim.MedicationText);
|
|
30
31
|
}
|
|
31
32
|
export function setMedicationText(claims, value) {
|
|
32
|
-
return setMedicationScalar(claims, MedicationStatementClaim.
|
|
33
|
+
return setMedicationScalar(claims, MedicationStatementClaim.CodeText, value);
|
|
33
34
|
}
|
|
34
35
|
export function getMedicationDoseQuantityValue(claims) {
|
|
35
36
|
return getMedicationNumber(claims, MedicationStatementClaimsFhirApiExtended.DoseQuantityValue);
|
|
@@ -6,17 +6,25 @@ export function medicationStatementFlatToFhirR4(claims) {
|
|
|
6
6
|
const subject = requireClaim(claims, MedicationStatementClaim.Subject);
|
|
7
7
|
const status = requireClaim(claims, MedicationStatementClaim.Status);
|
|
8
8
|
const effectiveDateTime = claims[MedicationStatementClaim.Effective];
|
|
9
|
-
const medicationText = claims[MedicationStatementClaim.
|
|
9
|
+
const medicationText = claims[MedicationStatementClaim.CodeText]
|
|
10
|
+
|| claims[MedicationStatementClaim.MedicationText];
|
|
11
|
+
const medicationReference = claims[MedicationStatementClaim.Medication];
|
|
10
12
|
const medicationIdentifier = claims[MedicationStatementClaim.MedicationIdentifier];
|
|
11
13
|
const medicationSerialNumber = claims[MedicationStatementClaim.MedicationSerialNumber];
|
|
12
14
|
const medicationExpirationDate = claims[MedicationStatementClaim.MedicationExpirationDate];
|
|
13
|
-
const hasContainedMedication = Boolean(medicationIdentifier) || Boolean(medicationSerialNumber) || Boolean(medicationExpirationDate)
|
|
15
|
+
const hasContainedMedication = Boolean(medicationIdentifier) || Boolean(medicationSerialNumber) || Boolean(medicationExpirationDate);
|
|
14
16
|
const containedMedicationId = 'medication-contained-1';
|
|
15
17
|
const containedMedication = hasContainedMedication ? {
|
|
16
18
|
resourceType: 'Medication',
|
|
17
19
|
id: containedMedicationId,
|
|
18
20
|
identifier: medicationIdentifier ? [{ value: medicationIdentifier }] : undefined,
|
|
19
|
-
code: medicationText ? {
|
|
21
|
+
code: claims[MedicationStatementClaim.Code] || medicationText ? {
|
|
22
|
+
coding: codingFromValue(claims[MedicationStatementClaim.Code])?.map((coding) => ({
|
|
23
|
+
...coding,
|
|
24
|
+
...(claims[MedicationStatementClaim.CodeDisplay] ? { display: claims[MedicationStatementClaim.CodeDisplay] } : {}),
|
|
25
|
+
})),
|
|
26
|
+
...(medicationText ? { text: medicationText } : {}),
|
|
27
|
+
} : undefined,
|
|
20
28
|
batch: medicationSerialNumber || medicationExpirationDate ? {
|
|
21
29
|
lotNumber: medicationSerialNumber,
|
|
22
30
|
expirationDate: medicationExpirationDate,
|
|
@@ -35,7 +43,7 @@ export function medicationStatementFlatToFhirR4(claims) {
|
|
|
35
43
|
status,
|
|
36
44
|
subject: { reference: subject },
|
|
37
45
|
effectiveDateTime,
|
|
38
|
-
medicationCodeableConcept: claims[MedicationStatementClaim.Code]
|
|
46
|
+
medicationCodeableConcept: !medicationReference && !hasContainedMedication && claims[MedicationStatementClaim.Code]
|
|
39
47
|
? {
|
|
40
48
|
coding: codingFromValue(claims[MedicationStatementClaim.Code])?.map((coding) => ({
|
|
41
49
|
...coding,
|
|
@@ -43,8 +51,10 @@ export function medicationStatementFlatToFhirR4(claims) {
|
|
|
43
51
|
})),
|
|
44
52
|
...(medicationText ? { text: medicationText } : {}),
|
|
45
53
|
}
|
|
46
|
-
: (!hasContainedMedication && medicationText ? { text: medicationText } : undefined),
|
|
47
|
-
medicationReference:
|
|
54
|
+
: (!medicationReference && !hasContainedMedication && medicationText ? { text: medicationText } : undefined),
|
|
55
|
+
medicationReference: medicationReference
|
|
56
|
+
? { reference: medicationReference }
|
|
57
|
+
: (hasContainedMedication ? { reference: `#${containedMedicationId}` } : undefined),
|
|
48
58
|
contained: containedMedication ? [containedMedication] : undefined,
|
|
49
59
|
informationSource: claims[MedicationStatementClaim.Source] ? { reference: claims[MedicationStatementClaim.Source] } : undefined,
|
|
50
60
|
dosage: hasDosage ? [{
|
|
@@ -71,10 +81,11 @@ export function medicationStatementFhirR4ToFlat(resource) {
|
|
|
71
81
|
? containedResources.find((item) => item?.resourceType === 'Medication' && String(item?.id || '') === medicationReference.slice(1))
|
|
72
82
|
: containedResources.find((item) => item?.resourceType === 'Medication');
|
|
73
83
|
const containedMedicationIdentifier = containedMedication?.identifier?.[0]?.value;
|
|
74
|
-
const
|
|
84
|
+
const containedMedicationConcept = containedMedication?.code;
|
|
85
|
+
const containedMedicationText = containedMedicationConcept?.text;
|
|
75
86
|
const batch = containedMedication?.batch;
|
|
76
87
|
const medicationConcept = resource.medicationCodeableConcept;
|
|
77
|
-
const medicationCode = codingToValue(medicationConcept?.coding?.[0]);
|
|
88
|
+
const medicationCode = codingToValue(medicationConcept?.coding?.[0] || containedMedicationConcept?.coding?.[0]);
|
|
78
89
|
const medicationText = containedMedicationText || medicationConcept?.text || undefined;
|
|
79
90
|
const dosage = resource.dosage?.[0];
|
|
80
91
|
const repeat = dosage?.timing
|
|
@@ -88,8 +99,9 @@ export function medicationStatementFhirR4ToFlat(resource) {
|
|
|
88
99
|
[MedicationStatementClaim.Status]: resource.status,
|
|
89
100
|
[MedicationStatementClaim.Effective]: resource.effectiveDateTime,
|
|
90
101
|
[MedicationStatementClaim.Code]: medicationCode,
|
|
91
|
-
[MedicationStatementClaim.
|
|
92
|
-
[MedicationStatementClaim.
|
|
102
|
+
[MedicationStatementClaim.CodeText]: medicationText,
|
|
103
|
+
[MedicationStatementClaim.Medication]: medicationReference?.startsWith('#') ? undefined : medicationReference,
|
|
104
|
+
[MedicationStatementClaim.CodeDisplay]: medicationConcept?.coding?.[0]?.display || containedMedicationConcept?.coding?.[0]?.display,
|
|
93
105
|
[MedicationStatementClaim.Note]: resource.note?.[0]?.text,
|
|
94
106
|
[MedicationStatementClaim.DosageInstruction]: resource.dosage?.[0]?.text,
|
|
95
107
|
[MedicationStatementClaim.Source]: resource.informationSource?.reference,
|
|
@@ -75,7 +75,7 @@ export function buildMedicationEditingCommunicationSessionExample() {
|
|
|
75
75
|
[MedicationStatementClaim.Identifier]: EXAMPLE_MEDICATION_STATEMENT_IDENTIFIER,
|
|
76
76
|
[MedicationStatementClaim.Subject]: EXAMPLE_SUBJECT_DID,
|
|
77
77
|
[MedicationStatementClaim.Status]: EXAMPLE_MEDICATION_STATEMENT_STATUS,
|
|
78
|
-
[MedicationStatementClaim.
|
|
78
|
+
[MedicationStatementClaim.CodeText]: EXAMPLE_MEDICATION_STATEMENT_TEXT,
|
|
79
79
|
},
|
|
80
80
|
fullUrl: `urn:uuid:${EXAMPLE_MEDICATION_STATEMENT_IDENTIFIER}`,
|
|
81
81
|
});
|
|
@@ -59,7 +59,7 @@ export function buildIpsClinicalHistoryBundleExample() {
|
|
|
59
59
|
[MedicationStatementClaim.Subject]: EXAMPLE_SUBJECT_DID,
|
|
60
60
|
[MedicationStatementClaim.Category]: HealthcareBasicSections.HistoryOfMedicationUse.attributeValue,
|
|
61
61
|
[MedicationStatementClaim.Status]: EXAMPLE_MEDICATION_STATEMENT_STATUS,
|
|
62
|
-
[MedicationStatementClaim.
|
|
62
|
+
[MedicationStatementClaim.CodeText]: EXAMPLE_MEDICATION_STATEMENT_TEXT,
|
|
63
63
|
[MedicationStatementClaim.Effective]: '2026-05-05',
|
|
64
64
|
},
|
|
65
65
|
fullUrl: `urn:uuid:${EXAMPLE_MEDICATION_STATEMENT_IDENTIFIER}`,
|
package/dist/examples/shared.js
CHANGED
|
@@ -693,7 +693,7 @@ export function buildExampleMedicationIpsDocumentBundle(input) {
|
|
|
693
693
|
[MedicationStatementClaim.Identifier]: input.medication.identifier,
|
|
694
694
|
[MedicationStatementClaim.Subject]: subjectDid,
|
|
695
695
|
[MedicationStatementClaim.Status]: EXAMPLE_MEDICATION_STATEMENT_STATUS,
|
|
696
|
-
[MedicationStatementClaim.
|
|
696
|
+
[MedicationStatementClaim.CodeText]: input.medication.text,
|
|
697
697
|
[MedicationStatementClaim.Effective]: input.medication.effectiveDateTime,
|
|
698
698
|
[MedicationStatementClaim.Note]: input.medication.note,
|
|
699
699
|
[MedicationStatementClaim.Category]: input.medication.section || HealthcareBasicSections.HistoryOfMedicationUse.attributeValue,
|
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
import type { ClaimSpec } from './types';
|
|
2
|
+
/** Canonical FHIR R5 code system for `MedicationStatement.adherence.code`. */
|
|
3
|
+
export declare const MEDICATION_STATEMENT_ADHERENCE_CODE_SYSTEM: "http://hl7.org/fhir/CodeSystem/medication-statement-adherence";
|
|
4
|
+
/**
|
|
5
|
+
* Codes published by the FHIR R5 Medication Statement Adherence value set.
|
|
6
|
+
*
|
|
7
|
+
* The FHIR binding strength is `example`, so applications must accept other
|
|
8
|
+
* valid Coding values even when these helpers cover the published code system.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MedicationStatementAdherenceCodes: {
|
|
11
|
+
readonly Taking: "taking";
|
|
12
|
+
readonly TakingAsDirected: "taking-as-directed";
|
|
13
|
+
readonly TakingNotAsDirected: "taking-not-as-directed";
|
|
14
|
+
readonly NotTaking: "not-taking";
|
|
15
|
+
readonly OnHold: "on-hold";
|
|
16
|
+
readonly OnHoldAsDirected: "on-hold-as-directed";
|
|
17
|
+
readonly OnHoldNotAsDirected: "on-hold-not-as-directed";
|
|
18
|
+
readonly Stopped: "stopped";
|
|
19
|
+
readonly StoppedAsDirected: "stopped-as-directed";
|
|
20
|
+
readonly StoppedNotAsDirected: "stopped-not-as-directed";
|
|
21
|
+
readonly Unknown: "unknown";
|
|
22
|
+
};
|
|
23
|
+
export type MedicationStatementAdherenceCode = typeof MedicationStatementAdherenceCodes[keyof typeof MedicationStatementAdherenceCodes];
|
|
1
24
|
/**
|
|
2
25
|
* Canonical flat claim keys for the lightweight `MedicationStatement.*` mapping
|
|
3
26
|
* used by shared examples, GW ingestion, and converter roundtrip tests.
|
|
@@ -9,13 +32,26 @@ export declare const MedicationStatementClaim: {
|
|
|
9
32
|
readonly Status: "MedicationStatement.status";
|
|
10
33
|
readonly Category: "MedicationStatement.category";
|
|
11
34
|
readonly Effective: "MedicationStatement.effective";
|
|
35
|
+
/** Official token SearchParameter `code`; maps to the medication concept, not a root FHIR element. */
|
|
12
36
|
readonly Code: "MedicationStatement.code";
|
|
37
|
+
/** Local/manual `CodeableConcept.text` companion for the medication concept. */
|
|
38
|
+
readonly CodeText: "MedicationStatement.code-text";
|
|
39
|
+
/** Readable alias for frontend authors; the emitted claim remains `MedicationStatement.code-text`. */
|
|
40
|
+
readonly CodeTextLocal: "MedicationStatement.code-text";
|
|
41
|
+
/** Terminology `Coding.display` companion for the medication concept. */
|
|
42
|
+
readonly CodeDisplay: "MedicationStatement.code-display";
|
|
43
|
+
/** Official reference SearchParameter; maps to `medication.reference`. */
|
|
13
44
|
readonly Medication: "MedicationStatement.medication";
|
|
14
45
|
readonly PartOf: "MedicationStatement.part-of";
|
|
15
46
|
readonly Source: "MedicationStatement.source";
|
|
47
|
+
/** @deprecated Use `CodeText`; retained only for historical payload readback. */
|
|
16
48
|
readonly MedicationText: "MedicationStatement.medication-text";
|
|
17
|
-
/**
|
|
18
|
-
readonly
|
|
49
|
+
/** Official R5 token SearchParameter; its FHIRPath expression targets the adherence CodeableConcept. */
|
|
50
|
+
readonly Adherence: "MedicationStatement.adherence";
|
|
51
|
+
/** Local/manual CodeableConcept.text companion for the official `adherence` token. */
|
|
52
|
+
readonly AdherenceText: "MedicationStatement.adherence-text";
|
|
53
|
+
/** Terminology Coding.display companion for the official `adherence` token. */
|
|
54
|
+
readonly AdherenceDisplay: "MedicationStatement.adherence-display";
|
|
19
55
|
readonly UserSelected: "MedicationStatement.user-selected";
|
|
20
56
|
/**
|
|
21
57
|
* Free-text clinical note.
|
|
@@ -68,6 +104,8 @@ export declare const MedicationStatementClaim: {
|
|
|
68
104
|
readonly TimingPeriod: "MedicationStatement.timing-period";
|
|
69
105
|
readonly TimingPeriodUnit: "MedicationStatement.timing-period-unit";
|
|
70
106
|
};
|
|
107
|
+
export type MedicationStatementClaimKey = typeof MedicationStatementClaim[keyof typeof MedicationStatementClaim];
|
|
108
|
+
export declare const MedicationStatementClaimSpecs: ClaimSpec[];
|
|
71
109
|
/**
|
|
72
110
|
* Flat claims contract for MedicationStatement using FHIR API-like search params.
|
|
73
111
|
*
|
|
@@ -81,6 +119,7 @@ export declare const MedicationStatementClaim: {
|
|
|
81
119
|
* @basedon https://hl7.org/fhir/medicationstatement.html#search
|
|
82
120
|
*/
|
|
83
121
|
export declare enum MedicationStatementClaimsFhirApi {
|
|
122
|
+
Adherence = "org.hl7.fhir.api.MedicationStatement.adherence",
|
|
84
123
|
Category = "org.hl7.fhir.api.MedicationStatement.category",
|
|
85
124
|
Code = "org.hl7.fhir.api.MedicationStatement.code",
|
|
86
125
|
Effective = "org.hl7.fhir.api.MedicationStatement.effective",
|
|
@@ -97,6 +136,11 @@ export declare enum MedicationStatementClaimsFhirApi {
|
|
|
97
136
|
* These are intentionally scalarized to map cleanly to SQL columns.
|
|
98
137
|
*/
|
|
99
138
|
export declare enum MedicationStatementClaimsFhirApiExtended {
|
|
139
|
+
Adherence = "org.hl7.fhir.api.MedicationStatement.adherence",
|
|
140
|
+
AdherenceText = "org.hl7.fhir.api.MedicationStatement.adherence-text",
|
|
141
|
+
AdherenceDisplay = "org.hl7.fhir.api.MedicationStatement.adherence-display",
|
|
142
|
+
CodeText = "org.hl7.fhir.api.MedicationStatement.code-text",
|
|
143
|
+
CodeDisplay = "org.hl7.fhir.api.MedicationStatement.code-display",
|
|
100
144
|
Category = "org.hl7.fhir.api.MedicationStatement.category",
|
|
101
145
|
Code = "org.hl7.fhir.api.MedicationStatement.code",
|
|
102
146
|
Effective = "org.hl7.fhir.api.MedicationStatement.effective",
|
|
@@ -174,6 +218,7 @@ export declare enum MedicationStatementClaimsFhirApiExtended {
|
|
|
174
218
|
* by query builders and frontend filters.
|
|
175
219
|
*/
|
|
176
220
|
export declare const MedicationStatementSearchParamNames: {
|
|
221
|
+
readonly Adherence: "adherence";
|
|
177
222
|
readonly Category: "category";
|
|
178
223
|
readonly Code: "code";
|
|
179
224
|
readonly Effective: "effective";
|
|
@@ -248,6 +293,7 @@ export declare const MedicationStatementSearchParamNames: {
|
|
|
248
293
|
export type MedicationStatementSearchParamName = typeof MedicationStatementSearchParamNames[keyof typeof MedicationStatementSearchParamNames];
|
|
249
294
|
export declare const MedicationStatementSearchParamToClaimKey: Record<MedicationStatementSearchParamName, MedicationStatementClaimsFhirApi | MedicationStatementClaimsFhirApiExtended>;
|
|
250
295
|
export declare const MedicationStatementClaimsFhirApiMap: {
|
|
296
|
+
"org.hl7.fhir.api.MedicationStatement.adherence": StringConstructor;
|
|
251
297
|
"org.hl7.fhir.api.MedicationStatement.category": StringConstructor;
|
|
252
298
|
"org.hl7.fhir.api.MedicationStatement.code": StringConstructor;
|
|
253
299
|
"org.hl7.fhir.api.MedicationStatement.effective": StringConstructor;
|
|
@@ -260,6 +306,10 @@ export declare const MedicationStatementClaimsFhirApiMap: {
|
|
|
260
306
|
"org.hl7.fhir.api.MedicationStatement.subject": StringConstructor;
|
|
261
307
|
};
|
|
262
308
|
export declare const MedicationStatementClaimsFhirApiExtendedMap: {
|
|
309
|
+
"org.hl7.fhir.api.MedicationStatement.adherence-text": StringConstructor;
|
|
310
|
+
"org.hl7.fhir.api.MedicationStatement.adherence-display": StringConstructor;
|
|
311
|
+
"org.hl7.fhir.api.MedicationStatement.code-text": StringConstructor;
|
|
312
|
+
"org.hl7.fhir.api.MedicationStatement.code-display": StringConstructor;
|
|
263
313
|
"org.hl7.fhir.api.MedicationStatement.dose-quantity": StringConstructor;
|
|
264
314
|
"org.hl7.fhir.api.MedicationStatement.dose-quantity-value": NumberConstructor;
|
|
265
315
|
"org.hl7.fhir.api.MedicationStatement.dose-quantity-unit": StringConstructor;
|
|
@@ -320,6 +370,7 @@ export declare const MedicationStatementClaimsFhirApiExtendedMap: {
|
|
|
320
370
|
"org.hl7.fhir.api.MedicationStatement.timing-bounds-range-high": StringConstructor;
|
|
321
371
|
"org.hl7.fhir.api.MedicationStatement.timing-bounds-range-high-value": NumberConstructor;
|
|
322
372
|
"org.hl7.fhir.api.MedicationStatement.timing-bounds-range-high-unit": StringConstructor;
|
|
373
|
+
"org.hl7.fhir.api.MedicationStatement.adherence": StringConstructor;
|
|
323
374
|
"org.hl7.fhir.api.MedicationStatement.category": StringConstructor;
|
|
324
375
|
"org.hl7.fhir.api.MedicationStatement.code": StringConstructor;
|
|
325
376
|
"org.hl7.fhir.api.MedicationStatement.effective": StringConstructor;
|
|
@@ -337,6 +388,7 @@ export declare const MedicationStatementClaimsFhirApiExtendedMap: {
|
|
|
337
388
|
*/
|
|
338
389
|
export interface MedicationStatementClaimsFlat {
|
|
339
390
|
'@context'?: 'org.hl7.fhir.api';
|
|
391
|
+
[MedicationStatementClaimsFhirApi.Adherence]?: string;
|
|
340
392
|
[MedicationStatementClaimsFhirApi.Category]?: string;
|
|
341
393
|
[MedicationStatementClaimsFhirApi.Code]?: string;
|
|
342
394
|
[MedicationStatementClaimsFhirApi.Effective]?: string;
|
|
@@ -347,6 +399,10 @@ export interface MedicationStatementClaimsFlat {
|
|
|
347
399
|
[MedicationStatementClaimsFhirApi.Source]?: string;
|
|
348
400
|
[MedicationStatementClaimsFhirApi.Status]?: string;
|
|
349
401
|
[MedicationStatementClaimsFhirApi.Subject]?: string;
|
|
402
|
+
[MedicationStatementClaimsFhirApiExtended.AdherenceText]?: string;
|
|
403
|
+
[MedicationStatementClaimsFhirApiExtended.AdherenceDisplay]?: string;
|
|
404
|
+
[MedicationStatementClaimsFhirApiExtended.CodeText]?: string;
|
|
405
|
+
[MedicationStatementClaimsFhirApiExtended.CodeDisplay]?: string;
|
|
350
406
|
[MedicationStatementClaimsFhirApiExtended.DoseQuantity]?: string;
|
|
351
407
|
[MedicationStatementClaimsFhirApiExtended.DoseQuantityValue]?: number;
|
|
352
408
|
[MedicationStatementClaimsFhirApiExtended.DoseQuantityUnit]?: string;
|
|
@@ -395,8 +451,13 @@ export interface MedicationStatementClaimsFlat {
|
|
|
395
451
|
*/
|
|
396
452
|
export interface MedicationStatementClaimsContextualized {
|
|
397
453
|
'@context': 'org.hl7.fhir.api';
|
|
454
|
+
'MedicationStatement.adherence'?: string;
|
|
455
|
+
'MedicationStatement.adherence-text'?: string;
|
|
456
|
+
'MedicationStatement.adherence-display'?: string;
|
|
398
457
|
'MedicationStatement.category'?: string;
|
|
399
458
|
'MedicationStatement.code'?: string;
|
|
459
|
+
'MedicationStatement.code-text'?: string;
|
|
460
|
+
'MedicationStatement.code-display'?: string;
|
|
400
461
|
'MedicationStatement.effective'?: string;
|
|
401
462
|
'MedicationStatement.identifier'?: string;
|
|
402
463
|
'MedicationStatement.medication'?: string;
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
// src/models/fhir/MedicationStatement.claims.ts
|
|
2
2
|
// Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
|
|
3
|
+
/** Canonical FHIR R5 code system for `MedicationStatement.adherence.code`. */
|
|
4
|
+
export const MEDICATION_STATEMENT_ADHERENCE_CODE_SYSTEM = 'http://hl7.org/fhir/CodeSystem/medication-statement-adherence';
|
|
5
|
+
/**
|
|
6
|
+
* Codes published by the FHIR R5 Medication Statement Adherence value set.
|
|
7
|
+
*
|
|
8
|
+
* The FHIR binding strength is `example`, so applications must accept other
|
|
9
|
+
* valid Coding values even when these helpers cover the published code system.
|
|
10
|
+
*/
|
|
11
|
+
export const MedicationStatementAdherenceCodes = {
|
|
12
|
+
Taking: 'taking',
|
|
13
|
+
TakingAsDirected: 'taking-as-directed',
|
|
14
|
+
TakingNotAsDirected: 'taking-not-as-directed',
|
|
15
|
+
NotTaking: 'not-taking',
|
|
16
|
+
OnHold: 'on-hold',
|
|
17
|
+
OnHoldAsDirected: 'on-hold-as-directed',
|
|
18
|
+
OnHoldNotAsDirected: 'on-hold-not-as-directed',
|
|
19
|
+
Stopped: 'stopped',
|
|
20
|
+
StoppedAsDirected: 'stopped-as-directed',
|
|
21
|
+
StoppedNotAsDirected: 'stopped-not-as-directed',
|
|
22
|
+
Unknown: 'unknown',
|
|
23
|
+
};
|
|
3
24
|
/**
|
|
4
25
|
* Canonical flat claim keys for the lightweight `MedicationStatement.*` mapping
|
|
5
26
|
* used by shared examples, GW ingestion, and converter roundtrip tests.
|
|
@@ -11,13 +32,26 @@ export const MedicationStatementClaim = {
|
|
|
11
32
|
Status: 'MedicationStatement.status',
|
|
12
33
|
Category: 'MedicationStatement.category',
|
|
13
34
|
Effective: 'MedicationStatement.effective',
|
|
35
|
+
/** Official token SearchParameter `code`; maps to the medication concept, not a root FHIR element. */
|
|
14
36
|
Code: 'MedicationStatement.code',
|
|
37
|
+
/** Local/manual `CodeableConcept.text` companion for the medication concept. */
|
|
38
|
+
CodeText: 'MedicationStatement.code-text',
|
|
39
|
+
/** Readable alias for frontend authors; the emitted claim remains `MedicationStatement.code-text`. */
|
|
40
|
+
CodeTextLocal: 'MedicationStatement.code-text',
|
|
41
|
+
/** Terminology `Coding.display` companion for the medication concept. */
|
|
42
|
+
CodeDisplay: 'MedicationStatement.code-display',
|
|
43
|
+
/** Official reference SearchParameter; maps to `medication.reference`. */
|
|
15
44
|
Medication: 'MedicationStatement.medication',
|
|
16
45
|
PartOf: 'MedicationStatement.part-of',
|
|
17
46
|
Source: 'MedicationStatement.source',
|
|
47
|
+
/** @deprecated Use `CodeText`; retained only for historical payload readback. */
|
|
18
48
|
MedicationText: 'MedicationStatement.medication-text',
|
|
19
|
-
/**
|
|
20
|
-
|
|
49
|
+
/** Official R5 token SearchParameter; its FHIRPath expression targets the adherence CodeableConcept. */
|
|
50
|
+
Adherence: 'MedicationStatement.adherence',
|
|
51
|
+
/** Local/manual CodeableConcept.text companion for the official `adherence` token. */
|
|
52
|
+
AdherenceText: 'MedicationStatement.adherence-text',
|
|
53
|
+
/** Terminology Coding.display companion for the official `adherence` token. */
|
|
54
|
+
AdherenceDisplay: 'MedicationStatement.adherence-display',
|
|
21
55
|
UserSelected: 'MedicationStatement.user-selected',
|
|
22
56
|
/**
|
|
23
57
|
* Free-text clinical note.
|
|
@@ -70,6 +104,15 @@ export const MedicationStatementClaim = {
|
|
|
70
104
|
TimingPeriod: 'MedicationStatement.timing-period',
|
|
71
105
|
TimingPeriodUnit: 'MedicationStatement.timing-period-unit',
|
|
72
106
|
};
|
|
107
|
+
export const MedicationStatementClaimSpecs = [
|
|
108
|
+
{ key: MedicationStatementClaim.Code, meaning: 'Official token SearchParameter for medication.concept.', example: 'http://www.nlm.nih.gov/research/umls/rxnorm|5640' },
|
|
109
|
+
{ key: MedicationStatementClaim.CodeText, meaning: 'Local/manual medication concept text matching resource language.', example: 'Ibuprofeno' },
|
|
110
|
+
{ key: MedicationStatementClaim.CodeDisplay, meaning: 'Canonical/international medication Coding.display.', example: 'Ibuprofen' },
|
|
111
|
+
{ key: MedicationStatementClaim.Medication, meaning: 'Official reference SearchParameter for medication.reference.', example: 'Medication/medication-123' },
|
|
112
|
+
{ key: MedicationStatementClaim.Adherence, meaning: 'Official R5 adherence token SearchParameter.', example: `${MEDICATION_STATEMENT_ADHERENCE_CODE_SYSTEM}|taking-as-directed` },
|
|
113
|
+
{ key: MedicationStatementClaim.AdherenceText, meaning: 'Local/manual adherence CodeableConcept.text.', example: 'Tomando según indicación' },
|
|
114
|
+
{ key: MedicationStatementClaim.AdherenceDisplay, meaning: 'Canonical adherence Coding.display.', example: 'Taking As Directed' },
|
|
115
|
+
];
|
|
73
116
|
/**
|
|
74
117
|
* Flat claims contract for MedicationStatement using FHIR API-like search params.
|
|
75
118
|
*
|
|
@@ -84,6 +127,7 @@ export const MedicationStatementClaim = {
|
|
|
84
127
|
*/
|
|
85
128
|
export var MedicationStatementClaimsFhirApi;
|
|
86
129
|
(function (MedicationStatementClaimsFhirApi) {
|
|
130
|
+
MedicationStatementClaimsFhirApi["Adherence"] = "org.hl7.fhir.api.MedicationStatement.adherence";
|
|
87
131
|
MedicationStatementClaimsFhirApi["Category"] = "org.hl7.fhir.api.MedicationStatement.category";
|
|
88
132
|
MedicationStatementClaimsFhirApi["Code"] = "org.hl7.fhir.api.MedicationStatement.code";
|
|
89
133
|
MedicationStatementClaimsFhirApi["Effective"] = "org.hl7.fhir.api.MedicationStatement.effective";
|
|
@@ -101,6 +145,11 @@ export var MedicationStatementClaimsFhirApi;
|
|
|
101
145
|
*/
|
|
102
146
|
export var MedicationStatementClaimsFhirApiExtended;
|
|
103
147
|
(function (MedicationStatementClaimsFhirApiExtended) {
|
|
148
|
+
MedicationStatementClaimsFhirApiExtended["Adherence"] = "org.hl7.fhir.api.MedicationStatement.adherence";
|
|
149
|
+
MedicationStatementClaimsFhirApiExtended["AdherenceText"] = "org.hl7.fhir.api.MedicationStatement.adherence-text";
|
|
150
|
+
MedicationStatementClaimsFhirApiExtended["AdherenceDisplay"] = "org.hl7.fhir.api.MedicationStatement.adherence-display";
|
|
151
|
+
MedicationStatementClaimsFhirApiExtended["CodeText"] = "org.hl7.fhir.api.MedicationStatement.code-text";
|
|
152
|
+
MedicationStatementClaimsFhirApiExtended["CodeDisplay"] = "org.hl7.fhir.api.MedicationStatement.code-display";
|
|
104
153
|
MedicationStatementClaimsFhirApiExtended["Category"] = "org.hl7.fhir.api.MedicationStatement.category";
|
|
105
154
|
MedicationStatementClaimsFhirApiExtended["Code"] = "org.hl7.fhir.api.MedicationStatement.code";
|
|
106
155
|
MedicationStatementClaimsFhirApiExtended["Effective"] = "org.hl7.fhir.api.MedicationStatement.effective";
|
|
@@ -181,6 +230,7 @@ export var MedicationStatementClaimsFhirApiExtended;
|
|
|
181
230
|
* by query builders and frontend filters.
|
|
182
231
|
*/
|
|
183
232
|
export const MedicationStatementSearchParamNames = {
|
|
233
|
+
Adherence: 'adherence',
|
|
184
234
|
Category: 'category',
|
|
185
235
|
Code: 'code',
|
|
186
236
|
Effective: 'effective',
|
|
@@ -253,6 +303,7 @@ export const MedicationStatementSearchParamNames = {
|
|
|
253
303
|
TimingBoundsRangeHighUnit: 'timing-bounds-range-high-unit',
|
|
254
304
|
};
|
|
255
305
|
export const MedicationStatementSearchParamToClaimKey = {
|
|
306
|
+
[MedicationStatementSearchParamNames.Adherence]: MedicationStatementClaimsFhirApi.Adherence,
|
|
256
307
|
[MedicationStatementSearchParamNames.Category]: MedicationStatementClaimsFhirApi.Category,
|
|
257
308
|
[MedicationStatementSearchParamNames.Code]: MedicationStatementClaimsFhirApi.Code,
|
|
258
309
|
[MedicationStatementSearchParamNames.Effective]: MedicationStatementClaimsFhirApi.Effective,
|
|
@@ -325,6 +376,7 @@ export const MedicationStatementSearchParamToClaimKey = {
|
|
|
325
376
|
[MedicationStatementSearchParamNames.TimingBoundsRangeHighUnit]: MedicationStatementClaimsFhirApiExtended.TimingBoundsRangeHighUnit,
|
|
326
377
|
};
|
|
327
378
|
export const MedicationStatementClaimsFhirApiMap = {
|
|
379
|
+
[MedicationStatementClaimsFhirApi.Adherence]: String,
|
|
328
380
|
[MedicationStatementClaimsFhirApi.Category]: String,
|
|
329
381
|
[MedicationStatementClaimsFhirApi.Code]: String,
|
|
330
382
|
[MedicationStatementClaimsFhirApi.Effective]: String,
|
|
@@ -338,6 +390,10 @@ export const MedicationStatementClaimsFhirApiMap = {
|
|
|
338
390
|
};
|
|
339
391
|
export const MedicationStatementClaimsFhirApiExtendedMap = {
|
|
340
392
|
...MedicationStatementClaimsFhirApiMap,
|
|
393
|
+
[MedicationStatementClaimsFhirApiExtended.AdherenceText]: String,
|
|
394
|
+
[MedicationStatementClaimsFhirApiExtended.AdherenceDisplay]: String,
|
|
395
|
+
[MedicationStatementClaimsFhirApiExtended.CodeText]: String,
|
|
396
|
+
[MedicationStatementClaimsFhirApiExtended.CodeDisplay]: String,
|
|
341
397
|
[MedicationStatementClaimsFhirApiExtended.DoseQuantity]: String,
|
|
342
398
|
[MedicationStatementClaimsFhirApiExtended.DoseQuantityValue]: Number,
|
|
343
399
|
[MedicationStatementClaimsFhirApiExtended.DoseQuantityUnit]: String,
|
|
@@ -374,8 +374,8 @@ function resolveTitle(resourceType, claims, resource, options = {}) {
|
|
|
374
374
|
}
|
|
375
375
|
if (resourceType === ResourceTypesFhirR4.MedicationStatement) {
|
|
376
376
|
const text = firstClaimValue(claims, [
|
|
377
|
+
MedicationStatementClaim.CodeText,
|
|
377
378
|
MedicationStatementClaim.MedicationText,
|
|
378
|
-
MedicationStatementClaimsFhirApi.Medication,
|
|
379
379
|
]);
|
|
380
380
|
const codeText = findFirstClaimBySuffixes(claims, ['.code-text', '.code-text-local', '.codetextlocal']);
|
|
381
381
|
const codeDisplay = findFirstClaimBySuffixes(claims, ['.code-display', '.codedisplay']);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { FhirResource } from '../convert/convert-shared';
|
|
2
|
+
export declare const FHIR_R4_PATIENT_ANIMAL_EXTENSION_URL = "http://hl7.org/fhir/StructureDefinition/patient-animal";
|
|
3
|
+
export type ConnectedDeviceSubjectKind = 'animal' | 'person';
|
|
4
|
+
export type ConnectedDeviceFhirR4Bundle = Readonly<{
|
|
5
|
+
resourceType: 'Bundle';
|
|
6
|
+
type: 'transaction';
|
|
7
|
+
entry: Array<{
|
|
8
|
+
fullUrl?: string;
|
|
9
|
+
resource: FhirResource;
|
|
10
|
+
request: {
|
|
11
|
+
method: 'PUT';
|
|
12
|
+
url: string;
|
|
13
|
+
};
|
|
14
|
+
}>;
|
|
15
|
+
}>;
|
|
16
|
+
export type ConnectedDeviceObservationInput = Readonly<{
|
|
17
|
+
id: string;
|
|
18
|
+
identifierSystem: string;
|
|
19
|
+
identifierValue: string;
|
|
20
|
+
status: 'final' | 'amended' | 'corrected';
|
|
21
|
+
codeSystem: string;
|
|
22
|
+
code: string;
|
|
23
|
+
display?: string;
|
|
24
|
+
effectiveDateTime: string;
|
|
25
|
+
value: number;
|
|
26
|
+
unit: string;
|
|
27
|
+
unitSystem: string;
|
|
28
|
+
unitCode: string;
|
|
29
|
+
}>;
|
|
30
|
+
export type BuildConnectedDeviceFhirR4BundleInput = Readonly<{
|
|
31
|
+
subjectKind: ConnectedDeviceSubjectKind;
|
|
32
|
+
subjectIdentifierSystem: string;
|
|
33
|
+
subjectIdentifier: string;
|
|
34
|
+
subjectResourceId: string;
|
|
35
|
+
animalSpecies?: Readonly<{
|
|
36
|
+
system: string;
|
|
37
|
+
code: string;
|
|
38
|
+
display?: string;
|
|
39
|
+
}>;
|
|
40
|
+
organizationReference: string;
|
|
41
|
+
device: Readonly<{
|
|
42
|
+
id: string;
|
|
43
|
+
identifierSystem: string;
|
|
44
|
+
identifierValue: string;
|
|
45
|
+
manufacturer?: string;
|
|
46
|
+
modelNumber?: string;
|
|
47
|
+
}>;
|
|
48
|
+
observations: readonly ConnectedDeviceObservationInput[];
|
|
49
|
+
}>;
|
|
50
|
+
export type NormalizedConnectedDeviceFhirR4Bundle = Readonly<{
|
|
51
|
+
subjectIdentifier: string;
|
|
52
|
+
subjectKind: ConnectedDeviceSubjectKind;
|
|
53
|
+
device: Readonly<{
|
|
54
|
+
resource: FhirResource;
|
|
55
|
+
claims: Readonly<Record<string, string>>;
|
|
56
|
+
}>;
|
|
57
|
+
observations: readonly Readonly<{
|
|
58
|
+
resource: FhirResource;
|
|
59
|
+
claims: Readonly<Record<string, string>>;
|
|
60
|
+
}>[];
|
|
61
|
+
provenance: readonly FhirResource[];
|
|
62
|
+
sourceBundle: ConnectedDeviceFhirR4Bundle;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Builds the minimum FHIR R4 transaction accepted from a device manufacturer
|
|
66
|
+
* or clinic. `Patient` is the interoperable wire resource for either subject
|
|
67
|
+
* kind; it does not replace the internal neutral Subject model.
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildConnectedDeviceFhirR4Bundle(input: BuildConnectedDeviceFhirR4BundleInput): ConnectedDeviceFhirR4Bundle;
|
|
70
|
+
/**
|
|
71
|
+
* Validates exact subject, Device, Observation and Provenance references, then
|
|
72
|
+
* projects registered clinical resources into scalar flat claims. Consent and
|
|
73
|
+
* SMART checks remain gateway responsibilities and must run before indexing.
|
|
74
|
+
*/
|
|
75
|
+
export declare function normalizeConnectedDeviceFhirR4Bundle(bundle: ConnectedDeviceFhirR4Bundle, options: Readonly<{
|
|
76
|
+
expectedSubjectIdentifier: string;
|
|
77
|
+
expectedSubjectIdentifierSystem: string;
|
|
78
|
+
expectedSubjectKind: ConnectedDeviceSubjectKind;
|
|
79
|
+
}>): NormalizedConnectedDeviceFhirR4Bundle;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { deviceFhirR4ToFlat } from '../convert/convert-device.js';
|
|
2
|
+
import { observationToFlatFhirR4 } from '../convert/convert-observation.js';
|
|
3
|
+
export const FHIR_R4_PATIENT_ANIMAL_EXTENSION_URL = 'http://hl7.org/fhir/StructureDefinition/patient-animal';
|
|
4
|
+
/**
|
|
5
|
+
* Builds the minimum FHIR R4 transaction accepted from a device manufacturer
|
|
6
|
+
* or clinic. `Patient` is the interoperable wire resource for either subject
|
|
7
|
+
* kind; it does not replace the internal neutral Subject model.
|
|
8
|
+
*/
|
|
9
|
+
export function buildConnectedDeviceFhirR4Bundle(input) {
|
|
10
|
+
assertStableIdentifier(input.subjectIdentifier, 'subjectIdentifier');
|
|
11
|
+
assertStableIdentifier(input.subjectIdentifierSystem, 'subjectIdentifierSystem');
|
|
12
|
+
assertId(input.subjectResourceId, 'subjectResourceId');
|
|
13
|
+
assertReference(input.organizationReference, 'organizationReference');
|
|
14
|
+
assertId(input.device.id, 'device.id');
|
|
15
|
+
if (!input.device.identifierSystem.trim() || !input.device.identifierValue.trim()) {
|
|
16
|
+
throw new Error('Connected Device requires identifier system and value.');
|
|
17
|
+
}
|
|
18
|
+
if (input.subjectKind === 'animal' && (!input.animalSpecies?.system.trim() || !input.animalSpecies.code.trim())) {
|
|
19
|
+
throw new Error('Animal FHIR Patient projection requires patient-animal species coding.');
|
|
20
|
+
}
|
|
21
|
+
if (input.observations.length === 0)
|
|
22
|
+
throw new Error('Connected Device bundle requires at least one Observation.');
|
|
23
|
+
const patientReference = `Patient/${input.subjectResourceId}`;
|
|
24
|
+
const deviceReference = `Device/${input.device.id}`;
|
|
25
|
+
const patient = {
|
|
26
|
+
resourceType: 'Patient',
|
|
27
|
+
id: input.subjectResourceId,
|
|
28
|
+
identifier: [{ system: input.subjectIdentifierSystem, value: input.subjectIdentifier }],
|
|
29
|
+
...(input.subjectKind === 'animal'
|
|
30
|
+
? {
|
|
31
|
+
extension: [{
|
|
32
|
+
url: FHIR_R4_PATIENT_ANIMAL_EXTENSION_URL,
|
|
33
|
+
extension: [{
|
|
34
|
+
url: 'species',
|
|
35
|
+
valueCodeableConcept: { coding: [{ ...input.animalSpecies }] },
|
|
36
|
+
}],
|
|
37
|
+
}],
|
|
38
|
+
}
|
|
39
|
+
: {}),
|
|
40
|
+
};
|
|
41
|
+
const device = {
|
|
42
|
+
resourceType: 'Device',
|
|
43
|
+
id: input.device.id,
|
|
44
|
+
identifier: [{ system: input.device.identifierSystem, value: input.device.identifierValue }],
|
|
45
|
+
status: 'active',
|
|
46
|
+
patient: { reference: patientReference },
|
|
47
|
+
owner: { reference: input.organizationReference },
|
|
48
|
+
...(input.device.manufacturer ? { manufacturer: input.device.manufacturer } : {}),
|
|
49
|
+
...(input.device.modelNumber ? { modelNumber: input.device.modelNumber } : {}),
|
|
50
|
+
};
|
|
51
|
+
const observations = input.observations.map((observation) => buildObservation(observation, patientReference, deviceReference, input.organizationReference));
|
|
52
|
+
const provenance = {
|
|
53
|
+
resourceType: 'Provenance',
|
|
54
|
+
id: 'device-measurement-provenance',
|
|
55
|
+
target: observations.map((observation) => ({ reference: `Observation/${String(observation.id)}` })),
|
|
56
|
+
recorded: input.observations.map((observation) => observation.effectiveDateTime).sort().at(-1),
|
|
57
|
+
agent: [{ type: { text: 'author' }, who: { reference: input.organizationReference } }],
|
|
58
|
+
entity: [{ role: 'source', what: { reference: deviceReference } }],
|
|
59
|
+
};
|
|
60
|
+
const resources = [patient, device, ...observations, provenance];
|
|
61
|
+
return {
|
|
62
|
+
resourceType: 'Bundle',
|
|
63
|
+
type: 'transaction',
|
|
64
|
+
entry: resources.map((resource) => ({
|
|
65
|
+
resource,
|
|
66
|
+
request: { method: 'PUT', url: `${resource.resourceType}/${String(resource.id)}` },
|
|
67
|
+
})),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validates exact subject, Device, Observation and Provenance references, then
|
|
72
|
+
* projects registered clinical resources into scalar flat claims. Consent and
|
|
73
|
+
* SMART checks remain gateway responsibilities and must run before indexing.
|
|
74
|
+
*/
|
|
75
|
+
export function normalizeConnectedDeviceFhirR4Bundle(bundle, options) {
|
|
76
|
+
if (bundle?.resourceType !== 'Bundle' || bundle.type !== 'transaction' || !Array.isArray(bundle.entry)) {
|
|
77
|
+
throw new Error('Connected Device payload must be a FHIR R4 transaction Bundle.');
|
|
78
|
+
}
|
|
79
|
+
const resources = bundle.entry.map((entry) => entry.resource);
|
|
80
|
+
const patient = requireExactlyOne(resources, 'Patient');
|
|
81
|
+
const device = requireExactlyOne(resources, 'Device');
|
|
82
|
+
const observations = resources.filter((resource) => resource.resourceType === 'Observation');
|
|
83
|
+
const provenance = resources.filter((resource) => resource.resourceType === 'Provenance');
|
|
84
|
+
if (observations.length === 0)
|
|
85
|
+
throw new Error('Connected Device bundle requires at least one Observation.');
|
|
86
|
+
if (provenance.length === 0)
|
|
87
|
+
throw new Error('Connected Device bundle requires Provenance.');
|
|
88
|
+
const subjectIdentifier = readIdentifierValue(patient, options.expectedSubjectIdentifier, options.expectedSubjectIdentifierSystem);
|
|
89
|
+
if (subjectIdentifier !== options.expectedSubjectIdentifier) {
|
|
90
|
+
throw new Error('FHIR Patient does not identify the exact authorized subject identifier.');
|
|
91
|
+
}
|
|
92
|
+
const hasAnimalExtension = (patient.extension || [])
|
|
93
|
+
.some((extension) => extension.url === FHIR_R4_PATIENT_ANIMAL_EXTENSION_URL);
|
|
94
|
+
if ((options.expectedSubjectKind === 'animal') !== hasAnimalExtension) {
|
|
95
|
+
throw new Error(`FHIR Patient subject kind does not match ${options.expectedSubjectKind}.`);
|
|
96
|
+
}
|
|
97
|
+
const patientReference = `Patient/${requiredResourceId(patient)}`;
|
|
98
|
+
const deviceReference = `Device/${requiredResourceId(device)}`;
|
|
99
|
+
if (referenceValue(device.patient) !== patientReference) {
|
|
100
|
+
throw new Error('Device.patient must reference the exact FHIR Patient projection.');
|
|
101
|
+
}
|
|
102
|
+
readFirstIdentifier(device, 'Device');
|
|
103
|
+
const provenanceTargets = new Set(provenance.flatMap((resource) => (resource.target || []).map((target) => String(target.reference || '').trim())));
|
|
104
|
+
for (const resource of provenance) {
|
|
105
|
+
const agents = resource.agent || [];
|
|
106
|
+
if (!agents.some((agent) => agent.who?.reference || agent.who?.identifier)) {
|
|
107
|
+
throw new Error('Provenance requires an accountable agent.');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const normalizedObservations = observations.map((observation) => {
|
|
111
|
+
const observationReference = `Observation/${requiredResourceId(observation)}`;
|
|
112
|
+
if (!provenanceTargets.has(observationReference)) {
|
|
113
|
+
throw new Error(`Provenance does not cover ${observationReference}.`);
|
|
114
|
+
}
|
|
115
|
+
validateObservation(observation, patientReference, deviceReference);
|
|
116
|
+
return { resource: observation, claims: scalarClaims(observationToFlatFhirR4(observation)) };
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
subjectIdentifier,
|
|
120
|
+
subjectKind: options.expectedSubjectKind,
|
|
121
|
+
device: { resource: device, claims: scalarClaims(deviceFhirR4ToFlat(device)) },
|
|
122
|
+
observations: normalizedObservations,
|
|
123
|
+
provenance,
|
|
124
|
+
sourceBundle: bundle,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function buildObservation(input, patientReference, deviceReference, organizationReference) {
|
|
128
|
+
assertId(input.id, 'observation.id');
|
|
129
|
+
if (!input.identifierSystem.trim() || !input.identifierValue.trim())
|
|
130
|
+
throw new Error('Observation identifier is required.');
|
|
131
|
+
if (!input.codeSystem.trim() || !input.code.trim())
|
|
132
|
+
throw new Error('Observation code system and code are required.');
|
|
133
|
+
if (!Number.isFinite(input.value))
|
|
134
|
+
throw new Error('Observation numeric value must be finite.');
|
|
135
|
+
if (!Number.isFinite(Date.parse(input.effectiveDateTime)))
|
|
136
|
+
throw new Error('Observation effectiveDateTime must be ISO 8601.');
|
|
137
|
+
return {
|
|
138
|
+
resourceType: 'Observation',
|
|
139
|
+
id: input.id,
|
|
140
|
+
identifier: [{ system: input.identifierSystem, value: input.identifierValue }],
|
|
141
|
+
status: input.status,
|
|
142
|
+
category: [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/observation-category', code: 'vital-signs' }] }],
|
|
143
|
+
code: { coding: [{ system: input.codeSystem, code: input.code, ...(input.display ? { display: input.display } : {}) }] },
|
|
144
|
+
subject: { reference: patientReference },
|
|
145
|
+
device: { reference: deviceReference },
|
|
146
|
+
performer: [{ reference: organizationReference }],
|
|
147
|
+
effectiveDateTime: input.effectiveDateTime,
|
|
148
|
+
valueQuantity: {
|
|
149
|
+
value: input.value,
|
|
150
|
+
unit: input.unit,
|
|
151
|
+
system: input.unitSystem,
|
|
152
|
+
code: input.unitCode,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function validateObservation(resource, patientReference, deviceReference) {
|
|
157
|
+
readFirstIdentifier(resource, 'Observation');
|
|
158
|
+
if (referenceValue(resource.subject) !== patientReference)
|
|
159
|
+
throw new Error('Observation.subject does not match the exact Patient.');
|
|
160
|
+
if (referenceValue(resource.device) !== deviceReference)
|
|
161
|
+
throw new Error('Observation.device does not match the registered Device.');
|
|
162
|
+
const coding = resource.code?.coding?.[0];
|
|
163
|
+
if (!coding?.system || !coding.code)
|
|
164
|
+
throw new Error('Observation requires a coded measurement.');
|
|
165
|
+
if (!resource.effectiveDateTime && !resource.issued)
|
|
166
|
+
throw new Error('Observation requires an effective or issued time.');
|
|
167
|
+
if (!resource.valueQuantity && !resource.valueCodeableConcept && resource.valueString === undefined) {
|
|
168
|
+
throw new Error('Observation requires a value.');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function requireExactlyOne(resources, resourceType) {
|
|
172
|
+
const matches = resources.filter((resource) => resource.resourceType === resourceType);
|
|
173
|
+
if (matches.length !== 1)
|
|
174
|
+
throw new Error(`Connected Device bundle requires exactly one ${resourceType}.`);
|
|
175
|
+
return matches[0];
|
|
176
|
+
}
|
|
177
|
+
function readIdentifierValue(resource, expected, expectedSystem) {
|
|
178
|
+
const identifiers = resource.identifier || [];
|
|
179
|
+
return String(identifiers.find((identifier) => identifier.system === expectedSystem && identifier.value === expected)?.value || '');
|
|
180
|
+
}
|
|
181
|
+
function readFirstIdentifier(resource, label) {
|
|
182
|
+
const value = String(resource.identifier?.[0]?.value || '').trim();
|
|
183
|
+
if (!value)
|
|
184
|
+
throw new Error(`${label} requires a stable identifier for replay protection.`);
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
function requiredResourceId(resource) {
|
|
188
|
+
const id = String(resource.id || '').trim();
|
|
189
|
+
assertId(id, `${resource.resourceType}.id`);
|
|
190
|
+
return id;
|
|
191
|
+
}
|
|
192
|
+
function referenceValue(value) {
|
|
193
|
+
return String(value?.reference || '').trim();
|
|
194
|
+
}
|
|
195
|
+
function scalarClaims(claims) {
|
|
196
|
+
return Object.freeze(Object.fromEntries(Object.entries(claims)
|
|
197
|
+
.filter((entry) => entry[1] !== undefined)
|
|
198
|
+
.map(([key, value]) => [key, String(value)])));
|
|
199
|
+
}
|
|
200
|
+
function assertStableIdentifier(value, field) {
|
|
201
|
+
if (!/^(?:did|urn|https):\S+$/i.test(String(value || '').trim()))
|
|
202
|
+
throw new Error(`${field} must be a stable URI.`);
|
|
203
|
+
}
|
|
204
|
+
function assertReference(value, field) {
|
|
205
|
+
if (!/^[A-Z][A-Za-z]+\/[A-Za-z0-9.-]+$/.test(String(value || '').trim()))
|
|
206
|
+
throw new Error(`${field} must be a FHIR reference.`);
|
|
207
|
+
}
|
|
208
|
+
function assertId(value, field) {
|
|
209
|
+
if (!/^[A-Za-z0-9.-]{1,64}$/.test(String(value || '').trim()))
|
|
210
|
+
throw new Error(`${field} must be a valid FHIR id.`);
|
|
211
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -106,6 +106,7 @@ export * from './smart-scope';
|
|
|
106
106
|
export * from './service-act-reasons';
|
|
107
107
|
export * from './same-as';
|
|
108
108
|
export * from './subject-identity';
|
|
109
|
+
export * from './connected-device-fhir-r4';
|
|
109
110
|
export * from './subject-identity-binding';
|
|
110
111
|
export * from './activation-request';
|
|
111
112
|
export * from './actor-identifier';
|
package/dist/utils/index.js
CHANGED
|
@@ -106,6 +106,7 @@ export * from './smart-scope.js';
|
|
|
106
106
|
export * from './service-act-reasons.js';
|
|
107
107
|
export * from './same-as.js';
|
|
108
108
|
export * from './subject-identity.js';
|
|
109
|
+
export * from './connected-device-fhir-r4.js';
|
|
109
110
|
export * from './subject-identity-binding.js';
|
|
110
111
|
export * from './activation-request.js';
|
|
111
112
|
export * from './actor-identifier.js';
|
|
@@ -31,15 +31,19 @@ export declare class MedicationStatementEntryEditor extends ClinicalResourceEntr
|
|
|
31
31
|
setEffective(value?: string | null): this;
|
|
32
32
|
/** Returns the effective date or period token. */
|
|
33
33
|
getEffective(): string | undefined;
|
|
34
|
-
/** Sets the
|
|
34
|
+
/** Sets the official `code` token SearchParameter value for `medication.concept`. */
|
|
35
35
|
setCode(code?: string | null): this;
|
|
36
|
-
/** Returns the
|
|
36
|
+
/** Returns the official `code` token SearchParameter value. */
|
|
37
37
|
getCode(): string | undefined;
|
|
38
|
-
/** Sets the
|
|
38
|
+
/** Sets the official `medication` reference SearchParameter value for `medication.reference`. */
|
|
39
|
+
setMedication(reference?: string | null): this;
|
|
40
|
+
/** Returns the official `medication` reference SearchParameter value. */
|
|
41
|
+
getMedication(): string | undefined;
|
|
42
|
+
/** @deprecated Prefer `setCodeTextLocal`; this compatibility method now emits canonical `code-text`. */
|
|
39
43
|
setMedicationText(text?: string | null): this;
|
|
40
|
-
/** Returns
|
|
44
|
+
/** Returns canonical `code-text`, falling back to historical `medication-text`. */
|
|
41
45
|
getMedicationText(): string | undefined;
|
|
42
|
-
/**
|
|
46
|
+
/** Sets local/manual `medication.concept.text` as canonical `code-text`. */
|
|
43
47
|
setCodeTextLocal(text?: string | null): this;
|
|
44
48
|
/** Returns the local-language medication text. */
|
|
45
49
|
getCodeTextLocal(): string | undefined;
|
|
@@ -47,6 +51,22 @@ export declare class MedicationStatementEntryEditor extends ClinicalResourceEntr
|
|
|
47
51
|
setCodeDisplay(display?: string | null): this;
|
|
48
52
|
/** Returns the English/international terminology display. */
|
|
49
53
|
getCodeDisplay(): string | undefined;
|
|
54
|
+
/** Sets the official R5 `adherence` token; the dotted HL7 FHIRPath is never emitted as a claim key. */
|
|
55
|
+
setAdherence(value?: string | null): this;
|
|
56
|
+
/** Returns the official R5 `adherence` token SearchParameter value. */
|
|
57
|
+
getAdherence(): string | undefined;
|
|
58
|
+
/** Convenience alias that emits the official R5 `adherence` SearchParameter claim. */
|
|
59
|
+
setAdherenceCode(value?: string | null): this;
|
|
60
|
+
/** Convenience alias that reads the official R5 `adherence` SearchParameter claim. */
|
|
61
|
+
getAdherenceCode(): string | undefined;
|
|
62
|
+
/** Sets local/manual text for the R5 adherence CodeableConcept. */
|
|
63
|
+
setAdherenceCodeTextLocal(value?: string | null): this;
|
|
64
|
+
/** Returns local/manual text for the R5 adherence CodeableConcept. */
|
|
65
|
+
getAdherenceCodeTextLocal(): string | undefined;
|
|
66
|
+
/** Sets the terminology display for the R5 adherence Coding. */
|
|
67
|
+
setAdherenceCodeDisplay(value?: string | null): this;
|
|
68
|
+
/** Returns the terminology display for the R5 adherence Coding. */
|
|
69
|
+
getAdherenceCodeDisplay(): string | undefined;
|
|
50
70
|
/** Sets one free-text note attached to the medication entry. */
|
|
51
71
|
setNote(note?: string | null): this;
|
|
52
72
|
/** Returns the current medication note. */
|
|
@@ -34,22 +34,45 @@ export class MedicationStatementEntryEditor extends ClinicalResourceEntryEditor
|
|
|
34
34
|
setEffective(value) { return this.setScalarClaim(MedicationStatementClaim.Effective, value); }
|
|
35
35
|
/** Returns the effective date or period token. */
|
|
36
36
|
getEffective() { return this.getScalarClaim(MedicationStatementClaim.Effective); }
|
|
37
|
-
/** Sets the
|
|
37
|
+
/** Sets the official `code` token SearchParameter value for `medication.concept`. */
|
|
38
38
|
setCode(code) { return this.setScalarClaim(MedicationStatementClaim.Code, code); }
|
|
39
|
-
/** Returns the
|
|
39
|
+
/** Returns the official `code` token SearchParameter value. */
|
|
40
40
|
getCode() { return this.getScalarClaim(MedicationStatementClaim.Code); }
|
|
41
|
-
/** Sets the
|
|
42
|
-
|
|
43
|
-
/** Returns the
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
|
|
41
|
+
/** Sets the official `medication` reference SearchParameter value for `medication.reference`. */
|
|
42
|
+
setMedication(reference) { return this.setScalarClaim(MedicationStatementClaim.Medication, reference); }
|
|
43
|
+
/** Returns the official `medication` reference SearchParameter value. */
|
|
44
|
+
getMedication() { return this.getScalarClaim(MedicationStatementClaim.Medication); }
|
|
45
|
+
/** @deprecated Prefer `setCodeTextLocal`; this compatibility method now emits canonical `code-text`. */
|
|
46
|
+
setMedicationText(text) { return this.setCodeTextLocal(text); }
|
|
47
|
+
/** Returns canonical `code-text`, falling back to historical `medication-text`. */
|
|
48
|
+
getMedicationText() { return this.getCodeTextLocal(); }
|
|
49
|
+
/** Sets local/manual `medication.concept.text` as canonical `code-text`. */
|
|
50
|
+
setCodeTextLocal(text) { return this.setScalarClaim(MedicationStatementClaim.CodeText, text); }
|
|
47
51
|
/** Returns the local-language medication text. */
|
|
48
|
-
getCodeTextLocal() {
|
|
52
|
+
getCodeTextLocal() {
|
|
53
|
+
return this.getScalarClaim(MedicationStatementClaim.CodeText)
|
|
54
|
+
|| this.getScalarClaim(MedicationStatementClaim.MedicationText);
|
|
55
|
+
}
|
|
49
56
|
/** Sets the English/international terminology display. */
|
|
50
57
|
setCodeDisplay(display) { return this.setScalarClaim(MedicationStatementClaim.CodeDisplay, display); }
|
|
51
58
|
/** Returns the English/international terminology display. */
|
|
52
59
|
getCodeDisplay() { return this.getScalarClaim(MedicationStatementClaim.CodeDisplay); }
|
|
60
|
+
/** Sets the official R5 `adherence` token; the dotted HL7 FHIRPath is never emitted as a claim key. */
|
|
61
|
+
setAdherence(value) { return this.setScalarClaim(MedicationStatementClaim.Adherence, value); }
|
|
62
|
+
/** Returns the official R5 `adherence` token SearchParameter value. */
|
|
63
|
+
getAdherence() { return this.getScalarClaim(MedicationStatementClaim.Adherence); }
|
|
64
|
+
/** Convenience alias that emits the official R5 `adherence` SearchParameter claim. */
|
|
65
|
+
setAdherenceCode(value) { return this.setAdherence(value); }
|
|
66
|
+
/** Convenience alias that reads the official R5 `adherence` SearchParameter claim. */
|
|
67
|
+
getAdherenceCode() { return this.getAdherence(); }
|
|
68
|
+
/** Sets local/manual text for the R5 adherence CodeableConcept. */
|
|
69
|
+
setAdherenceCodeTextLocal(value) { return this.setScalarClaim(MedicationStatementClaim.AdherenceText, value); }
|
|
70
|
+
/** Returns local/manual text for the R5 adherence CodeableConcept. */
|
|
71
|
+
getAdherenceCodeTextLocal() { return this.getScalarClaim(MedicationStatementClaim.AdherenceText); }
|
|
72
|
+
/** Sets the terminology display for the R5 adherence Coding. */
|
|
73
|
+
setAdherenceCodeDisplay(value) { return this.setScalarClaim(MedicationStatementClaim.AdherenceDisplay, value); }
|
|
74
|
+
/** Returns the terminology display for the R5 adherence Coding. */
|
|
75
|
+
getAdherenceCodeDisplay() { return this.getScalarClaim(MedicationStatementClaim.AdherenceDisplay); }
|
|
53
76
|
/** Sets one free-text note attached to the medication entry. */
|
|
54
77
|
setNote(note) { return this.setScalarClaim(MedicationStatementClaim.Note, note); }
|
|
55
78
|
/** Returns the current medication note. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VerifiableCredentialV2 } from '../models/verifiable-credential';
|
|
2
|
-
/** Lifecycle
|
|
2
|
+
/** Lifecycle for a separate postal-address verification credential. */
|
|
3
3
|
export declare const PostalActivationLicenseStatuses: Readonly<{
|
|
4
4
|
readonly Issued: "issued";
|
|
5
5
|
readonly Delivered: "delivered";
|
|
@@ -48,13 +48,18 @@ export type OrganizationTestNetworkCredentialInput = Readonly<{
|
|
|
48
48
|
applicationId: string;
|
|
49
49
|
accessPath: 'partner' | 'test-network';
|
|
50
50
|
targetNetwork: 'test-network' | 'network';
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* @deprecated Postal-address verification is a separate production-readiness
|
|
53
|
+
* flow. This compatibility input is ignored and is never embedded in a Test
|
|
54
|
+
* Network authorization credential.
|
|
55
|
+
*/
|
|
56
|
+
postalLicense?: PostalActivationLicenseBinding;
|
|
52
57
|
proof?: VerifiableCredentialV2['proof'];
|
|
53
58
|
}>;
|
|
54
59
|
/**
|
|
55
60
|
* Builds the immutable VC that a controller receives out-of-band and attaches
|
|
56
|
-
* to `Organization/_transaction`.
|
|
57
|
-
* credential
|
|
61
|
+
* to `Organization/_transaction`. Postal-address verification is deliberately
|
|
62
|
+
* outside this credential and may be completed independently before production.
|
|
58
63
|
*/
|
|
59
64
|
export declare function buildOrganizationTestNetworkCredential(input: OrganizationTestNetworkCredentialInput): VerifiableCredentialV2;
|
|
60
65
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContractCredentialTypes, W3cCredentialContexts, W3cCredentialTypes, } from '../constants/verifiable-credentials.js';
|
|
2
|
-
/** Lifecycle
|
|
2
|
+
/** Lifecycle for a separate postal-address verification credential. */
|
|
3
3
|
export const PostalActivationLicenseStatuses = Object.freeze({
|
|
4
4
|
Issued: 'issued',
|
|
5
5
|
Delivered: 'delivered',
|
|
@@ -15,23 +15,13 @@ function required(value, name) {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Builds the immutable VC that a controller receives out-of-band and attaches
|
|
18
|
-
* to `Organization/_transaction`.
|
|
19
|
-
* credential
|
|
18
|
+
* to `Organization/_transaction`. Postal-address verification is deliberately
|
|
19
|
+
* outside this credential and may be completed independently before production.
|
|
20
20
|
*/
|
|
21
21
|
export function buildOrganizationTestNetworkCredential(input) {
|
|
22
22
|
const applicationId = required(input.applicationId, 'applicationId');
|
|
23
23
|
const organizationIdentifier = required(input.organizationIdentifier, 'organizationIdentifier');
|
|
24
24
|
const controllerEmail = required(input.controllerEmail, 'controllerEmail').toLowerCase();
|
|
25
|
-
if (input.postalLicense.status !== PostalActivationLicenseStatuses.Delivered) {
|
|
26
|
-
throw new Error('Organization registration authorization requires a delivered postal activation licence.');
|
|
27
|
-
}
|
|
28
|
-
if (input.postalLicense.applicationId !== applicationId
|
|
29
|
-
|| input.postalLicense.organizationIdentifier !== organizationIdentifier
|
|
30
|
-
|| input.postalLicense.controllerEmail.trim().toLowerCase() !== controllerEmail
|
|
31
|
-
|| input.postalLicense.controllerKeyMaterial !== input.controllerKeyMaterial
|
|
32
|
-
|| input.postalLicense.network !== input.targetNetwork) {
|
|
33
|
-
throw new Error('Postal activation licence does not match the organization registration application.');
|
|
34
|
-
}
|
|
35
25
|
return {
|
|
36
26
|
'@context': [W3cCredentialContexts.V2, 'https://schema.org'],
|
|
37
27
|
id: required(input.credentialId, 'credentialId'),
|
|
@@ -53,17 +43,6 @@ export function buildOrganizationTestNetworkCredential(input) {
|
|
|
53
43
|
email: controllerEmail,
|
|
54
44
|
hasCredential: { material: required(input.controllerKeyMaterial, 'controllerKeyMaterial') },
|
|
55
45
|
},
|
|
56
|
-
postalActivationLicense: {
|
|
57
|
-
id: required(input.postalLicense.licenseId, 'postalLicense.licenseId'),
|
|
58
|
-
status: input.postalLicense.status,
|
|
59
|
-
postalAddressHash: required(input.postalLicense.postalAddressHash, 'postalLicense.postalAddressHash'),
|
|
60
|
-
deliveredAt: required(input.postalLicense.deliveredAt, 'postalLicense.deliveredAt'),
|
|
61
|
-
protectedCode: {
|
|
62
|
-
algorithm: input.postalLicense.protectedCode.algorithm,
|
|
63
|
-
salt: required(input.postalLicense.protectedCode.salt, 'postalLicense.protectedCode.salt'),
|
|
64
|
-
digest: required(input.postalLicense.protectedCode.digest, 'postalLicense.protectedCode.digest'),
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
46
|
},
|
|
68
47
|
validFrom: required(input.validFrom, 'validFrom'),
|
|
69
48
|
validUntil: required(input.validUntil, 'validUntil'),
|