gdc-common-utils-ts 2.5.8 → 2.5.10

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.
@@ -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.MedicationText);
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.MedicationText, value);
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.MedicationText];
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) || Boolean(medicationText);
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 ? { text: medicationText } : undefined,
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: hasContainedMedication ? { reference: `#${containedMedicationId}` } : undefined,
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 containedMedicationText = containedMedication?.code?.text;
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.MedicationText]: medicationText,
92
- [MedicationStatementClaim.CodeDisplay]: medicationConcept?.coding?.[0]?.display,
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.MedicationText]: EXAMPLE_MEDICATION_STATEMENT_TEXT,
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.MedicationText]: EXAMPLE_MEDICATION_STATEMENT_TEXT,
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}`,
@@ -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.MedicationText]: input.medication.text,
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
- /** English/international display for the coded medication. */
18
- readonly CodeDisplay: "MedicationStatement.code-display";
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
- /** English/international display for the coded medication. */
20
- CodeDisplay: 'MedicationStatement.code-display',
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
+ }
@@ -36,7 +36,10 @@ export declare function generateServiceId(selector: ServiceEndpointSelector): st
36
36
  * due to case sensitivity in the path component of the underlying URL.
37
37
  *
38
38
  * The rule is:
39
- * - All segments are lowercased, EXCEPT the final segment.
39
+ * - The DNS authority and ordinary non-final path segments are lowercased.
40
+ * - A hosted VAT tenant segment is uppercased (`vates-b...` -> `VATES-B...`).
41
+ * - A `cds-<jurisdiction>` segment uses an uppercase ISO jurisdiction.
42
+ * - The final segment is preserved.
40
43
  * - If the final segment represents a `system|code` pair (e.g., for a role),
41
44
  * the `system` part is lowercased, but the `code` is preserved as-is.
42
45
  * - If the final segment is a unique identifier (like a Tax ID), it is preserved as-is.
package/dist/utils/did.js CHANGED
@@ -56,7 +56,10 @@ export function generateServiceId(selector) {
56
56
  * due to case sensitivity in the path component of the underlying URL.
57
57
  *
58
58
  * The rule is:
59
- * - All segments are lowercased, EXCEPT the final segment.
59
+ * - The DNS authority and ordinary non-final path segments are lowercased.
60
+ * - A hosted VAT tenant segment is uppercased (`vates-b...` -> `VATES-B...`).
61
+ * - A `cds-<jurisdiction>` segment uses an uppercase ISO jurisdiction.
62
+ * - The final segment is preserved.
60
63
  * - If the final segment represents a `system|code` pair (e.g., for a role),
61
64
  * the `system` part is lowercased, but the `code` is preserved as-is.
62
65
  * - If the final segment is a unique identifier (like a Tax ID), it is preserved as-is.
@@ -70,16 +73,30 @@ export function normalizeDidWeb(did) {
70
73
  // Not a valid did:web, return as is.
71
74
  return did;
72
75
  }
73
- // Lowercase all parts except the very last one.
74
- const lowercasedParts = parts.slice(0, -1).map(part => part.toLowerCase());
75
- let lastPart = parts[parts.length - 1];
76
- // Special handling for the last part if it contains a role descriptor.
77
- if (lastPart.includes('|')) {
78
- const [system, ...codeParts] = lastPart.split('|');
79
- const code = codeParts.join('|'); // Re-join in case the code itself has a pipe.
80
- lastPart = `${system.toLowerCase()}|${code}`;
81
- }
82
- return [...lowercasedParts, lastPart].join(':');
76
+ const lastIndex = parts.length - 1;
77
+ return parts.map((part, index) => {
78
+ if (index < 2)
79
+ return part.toLowerCase();
80
+ if (index === 2)
81
+ return part.toLowerCase().replace(/%3a/gi, '%3A');
82
+ // Hosted tenant identifiers use the canonical VAT + ISO country prefix.
83
+ // Keep this deliberately narrow so unrelated opaque DID path identifiers
84
+ // are not modified merely because they contain the letters "vat".
85
+ if (/^vat[a-z]{2}[-a-z0-9._]+$/i.test(part)) {
86
+ return part.toUpperCase();
87
+ }
88
+ const jurisdictionMatch = /^cds-([a-z]{2})$/i.exec(part);
89
+ if (jurisdictionMatch) {
90
+ return `cds-${jurisdictionMatch[1].toUpperCase()}`;
91
+ }
92
+ if (index === lastIndex) {
93
+ if (!part.includes('|'))
94
+ return part;
95
+ const [system, ...codeParts] = part.split('|');
96
+ return `${system.toLowerCase()}|${codeParts.join('|')}`;
97
+ }
98
+ return part.toLowerCase();
99
+ }).join(':');
83
100
  }
84
101
  /**
85
102
  * Encodes a hostname according to did:web spec (percent-encodes port colons).
@@ -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';
@@ -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';
@@ -139,6 +139,35 @@ function extractClaims(entry) {
139
139
  const resourceClaims = resourceMeta.claims && typeof resourceMeta.claims === 'object' ? resourceMeta.claims : undefined;
140
140
  return { ...(resourceClaims || {}), ...(metaClaims || {}) };
141
141
  }
142
+ function isLicenseListEntry(entry) {
143
+ const meta = entry.meta && typeof entry.meta === 'object'
144
+ ? entry.meta
145
+ : {};
146
+ const claims = extractClaims(entry);
147
+ return !!(normalizeText(meta.status)
148
+ || normalizeText(entry.status)
149
+ || normalizeText(entry.id)
150
+ || normalizeText(claims[ClaimsIndividualProductSchemaorg.serialNumber])
151
+ || normalizeText(claims[ClaimsOfferSchemaorg.serialNumber]));
152
+ }
153
+ function extractLicenseListEntries(node) {
154
+ if (!node || typeof node !== 'object')
155
+ return [];
156
+ if (Array.isArray(node)) {
157
+ return node.flatMap((entry) => extractLicenseListEntries(entry));
158
+ }
159
+ const entry = node;
160
+ const resource = entry.resource && typeof entry.resource === 'object'
161
+ ? entry.resource
162
+ : undefined;
163
+ if (resource && Array.isArray(resource.data)) {
164
+ return extractLicenseListEntries(resource.data);
165
+ }
166
+ if (Array.isArray(entry.data)) {
167
+ return extractLicenseListEntries(entry.data);
168
+ }
169
+ return isLicenseListEntry(entry) ? [entry] : [];
170
+ }
142
171
  /**
143
172
  * Reads license-like search/list records from one current GW-style response
144
173
  * body without exposing raw claim access to frontend code.
@@ -146,7 +175,7 @@ function extractClaims(entry) {
146
175
  export function readLicenseListRecords(body) {
147
176
  const root = body && typeof body === 'object' ? body : {};
148
177
  const bodyNode = root.body && typeof root.body === 'object' ? root.body : root;
149
- const data = Array.isArray(bodyNode.data) ? bodyNode.data : [];
178
+ const data = extractLicenseListEntries(bodyNode);
150
179
  return data
151
180
  .filter((entry) => !!entry && typeof entry === 'object')
152
181
  .map((entry) => {
@@ -156,9 +185,9 @@ export function readLicenseListRecords(body) {
156
185
  id: normalizeText(claims[ClaimsIndividualProductSchemaorg.serialNumber]
157
186
  || claims[ClaimsOfferSchemaorg.serialNumber]
158
187
  || entry.id),
159
- status: normalizeText(meta.status),
160
- subjectId: normalizeText(meta.subjectId),
161
- ownerOrganizationId: normalizeText(meta.ownerOrganizationId || claims['License.ownerOrganizationId']),
188
+ status: normalizeText(meta.status || entry.status),
189
+ subjectId: normalizeText(meta.subjectId || entry.subjectId),
190
+ ownerOrganizationId: normalizeText(meta.ownerOrganizationId || entry.ownerOrganizationId || claims['License.ownerOrganizationId']),
162
191
  email: normalizeText(claims[ClaimsPersonSchemaorg.email]),
163
192
  telephone: normalizeText(claims[ClaimsPersonSchemaorg.telephone]),
164
193
  role: normalizeText(claims[ClaimsPersonSchemaorg.hasOccupationalRoleValue]),
@@ -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 coded medication identifier/value used by downstream FHIR export. */
34
+ /** Sets the official `code` token SearchParameter value for `medication.concept`. */
35
35
  setCode(code?: string | null): this;
36
- /** Returns the coded medication identifier/value. */
36
+ /** Returns the official `code` token SearchParameter value. */
37
37
  getCode(): string | undefined;
38
- /** Sets the human-readable medication text shown in UI cards and document narratives. */
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 the human-readable medication text. */
44
+ /** Returns canonical `code-text`, falling back to historical `medication-text`. */
41
45
  getMedicationText(): string | undefined;
42
- /** Alias used consistently by coded IPS editors for local FHIR text. */
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 coded medication identifier/value used by downstream FHIR export. */
37
+ /** Sets the official `code` token SearchParameter value for `medication.concept`. */
38
38
  setCode(code) { return this.setScalarClaim(MedicationStatementClaim.Code, code); }
39
- /** Returns the coded medication identifier/value. */
39
+ /** Returns the official `code` token SearchParameter value. */
40
40
  getCode() { return this.getScalarClaim(MedicationStatementClaim.Code); }
41
- /** Sets the human-readable medication text shown in UI cards and document narratives. */
42
- setMedicationText(text) { return this.setScalarClaim(MedicationStatementClaim.MedicationText, text); }
43
- /** Returns the human-readable medication text. */
44
- getMedicationText() { return this.getScalarClaim(MedicationStatementClaim.MedicationText); }
45
- /** Alias used consistently by coded IPS editors for local FHIR text. */
46
- setCodeTextLocal(text) { return this.setMedicationText(text); }
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() { return this.getMedicationText(); }
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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.5.8",
3
+ "version": "2.5.10",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },