gdc-common-utils-ts 2.3.26 → 2.3.28

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.
Files changed (58) hide show
  1. package/README.md +5 -0
  2. package/dist/claims/claims-helpers-related-person.js +0 -1
  3. package/dist/convert/convert-care-plan.js +25 -1
  4. package/dist/convert/convert-condition.js +12 -0
  5. package/dist/convert/convert-consent.js +59 -13
  6. package/dist/convert/convert-device-use-statement.js +3 -1
  7. package/dist/convert/convert-immunization.js +12 -2
  8. package/dist/convert/convert-medication-statement.js +35 -1
  9. package/dist/convert/convert-observation.js +43 -2
  10. package/dist/convert/convert-practitioner-role.d.ts +1 -1
  11. package/dist/convert/convert-practitioner-role.js +41 -13
  12. package/dist/convert/convert-shared.js +1 -1
  13. package/dist/examples/communication-didcomm-payload.d.ts +2 -2
  14. package/dist/examples/related-person.d.ts +9 -9
  15. package/dist/models/consent-rule.d.ts +8 -1
  16. package/dist/models/consent-rule.js +8 -1
  17. package/dist/models/indexing.d.ts +5 -5
  18. package/dist/models/interoperable-claims/appointment-claims.d.ts +57 -57
  19. package/dist/models/interoperable-claims/appointment-claims.js +31 -32
  20. package/dist/models/interoperable-claims/appointment-response-claims.d.ts +11 -11
  21. package/dist/models/interoperable-claims/appointment-response-claims.js +11 -11
  22. package/dist/models/interoperable-claims/care-plan-claims.d.ts +11 -0
  23. package/dist/models/interoperable-claims/care-plan-claims.js +11 -0
  24. package/dist/models/interoperable-claims/clinical-impression-claims.d.ts +1 -1
  25. package/dist/models/interoperable-claims/clinical-impression-claims.js +1 -1
  26. package/dist/models/interoperable-claims/condition-claims.d.ts +2 -0
  27. package/dist/models/interoperable-claims/condition-claims.js +2 -0
  28. package/dist/models/interoperable-claims/device-use-statement-claims.d.ts +3 -2
  29. package/dist/models/interoperable-claims/device-use-statement-claims.js +3 -2
  30. package/dist/models/interoperable-claims/document-reference-claims.d.ts +1 -1
  31. package/dist/models/interoperable-claims/document-reference-claims.js +1 -1
  32. package/dist/models/interoperable-claims/immunization-claims.d.ts +4 -0
  33. package/dist/models/interoperable-claims/immunization-claims.js +4 -0
  34. package/dist/models/interoperable-claims/medication-statement-claims.d.ts +6 -0
  35. package/dist/models/interoperable-claims/medication-statement-claims.js +6 -0
  36. package/dist/models/interoperable-claims/observation-claims.d.ts +12 -4
  37. package/dist/models/interoperable-claims/observation-claims.js +10 -2
  38. package/dist/models/interoperable-claims/practitioner-role-claims.d.ts +9 -0
  39. package/dist/models/interoperable-claims/practitioner-role-claims.js +9 -0
  40. package/dist/models/interoperable-claims/related-person-claims.d.ts +3 -2
  41. package/dist/models/interoperable-claims/related-person-claims.js +3 -2
  42. package/dist/models/params.d.ts +1 -1
  43. package/dist/utils/bundle-document-builder.js +37 -16
  44. package/dist/utils/bundle-entry-editor.d.ts +13 -0
  45. package/dist/utils/bundle-entry-editor.js +18 -0
  46. package/dist/utils/clinical-resource-converters.js +6 -4
  47. package/dist/utils/clinical-resource-view.d.ts +55 -0
  48. package/dist/utils/clinical-resource-view.js +137 -1
  49. package/dist/utils/communication-bundle-document-request.js +1 -1
  50. package/dist/utils/communication-fhir-r4.js +1 -1
  51. package/dist/utils/fhir-api-claim-helpers.d.ts +2 -0
  52. package/dist/utils/fhir-api-claim-helpers.js +16 -0
  53. package/dist/utils/ips-bundle-claims.d.ts +3 -4
  54. package/dist/utils/ips-bundle-claims.js +7 -7
  55. package/dist/utils/medication-statement-entry-editor.js +10 -10
  56. package/dist/utils/observation-component-entry-editor.d.ts +2 -2
  57. package/dist/utils/observation-component-entry-editor.js +2 -2
  58. package/package.json +1 -1
@@ -100,6 +100,14 @@ export declare const ObservationClaim: {
100
100
  readonly ValueQuantityNumber: "Observation.value-quantity-number";
101
101
  /** Human-readable quantity unit. Example: `/min`, `mmHg`, `Cel`. */
102
102
  readonly ValueQuantityUnit: "Observation.value-quantity-unit";
103
+ readonly ReferenceRangeLowNumber: "Observation.reference-range-low-number";
104
+ readonly ReferenceRangeHighNumber: "Observation.reference-range-high-number";
105
+ readonly ReferenceRangeUnit: "Observation.reference-range-unit";
106
+ readonly ReferenceRangeText: "Observation.reference-range-text";
107
+ readonly ComponentCode: "Observation.component-code";
108
+ readonly ComponentCodeDisplay: "Observation.component-code-display";
109
+ readonly ComponentValueQuantityNumber: "Observation.component-value-quantity-number";
110
+ readonly ComponentValueQuantityUnit: "Observation.component-value-quantity-unit";
103
111
  /** Aggregated score total for composite assessment observations when present. Example: `8`. */
104
112
  readonly ScoreTotalNumber: "Observation.score-total-number";
105
113
  /** Indexed systolic scalar copied onto the parent blood pressure observation. Example: `120`. */
@@ -110,8 +118,8 @@ export declare const ObservationClaim: {
110
118
  readonly ValueString: "Observation.value-string";
111
119
  /** User-authored note. Example: `Measured after resting 5 minutes.` */
112
120
  readonly Note: "Observation.note";
113
- /** FHIR-compatible explicit `effectiveDateTime` fallback. Example: `2026-06-01T10:00:00Z`. */
114
- readonly EffectiveDateTime: "Observation.effectiveDateTime";
121
+ /** FHIR-compatible explicit `effectiveDateTime` fallback using the canonical kebab-case claim name. Example: `2026-06-01T10:00:00Z`. */
122
+ readonly EffectiveDateTime: "Observation.effective-datetime";
115
123
  };
116
124
  export type ObservationClaimKey = typeof ObservationClaim[keyof typeof ObservationClaim];
117
125
  export type SplitCodingClaims = Readonly<{
@@ -259,14 +267,14 @@ export type ClaimsVitalSign = ClaimsObservationVitalSigns;
259
267
  * Free-text or UI-only fields may exist here even when they must stay out of
260
268
  * blind-query/search indexes.
261
269
  */
262
- export declare const ObservationGeneralClaimsList: readonly ["Observation.based-on", "Observation.category", "Observation.code-system", "Observation.code-value", "Observation.code-text", "Observation.code-display", "Observation.code", "Observation.date", "Observation.device", "Observation.encounter", "Observation.effectiveDateTime", "Observation.focus", "Observation.has-member", "Observation.component-tags", "Observation.component-code-values", "Observation.component-names", "Observation.identifier", "Observation.language", "Observation.method", "Observation.note", "Observation.patient", "Observation.performer", "Observation.specimen", "Observation.status", "Observation.subject", "Observation.value-concept-system", "Observation.value-concept-value", "Observation.value-concept-text", "Observation.value-concept-display", "Observation.value-concept", "Observation.value-date", "Observation.value-quantity-comparator", "Observation.value-quantity-number", "Observation.value-quantity-unit", "Observation.score-total-number", "Observation.bp-systolic-number", "Observation.bp-diastolic-number", "Observation.value-string"];
270
+ export declare const ObservationGeneralClaimsList: readonly ["Observation.based-on", "Observation.category", "Observation.code-system", "Observation.code-value", "Observation.code-text", "Observation.code-display", "Observation.code", "Observation.date", "Observation.device", "Observation.encounter", "Observation.effective-datetime", "Observation.focus", "Observation.has-member", "Observation.component-tags", "Observation.component-code-values", "Observation.component-names", "Observation.identifier", "Observation.language", "Observation.method", "Observation.note", "Observation.patient", "Observation.performer", "Observation.specimen", "Observation.status", "Observation.subject", "Observation.value-concept-system", "Observation.value-concept-value", "Observation.value-concept-text", "Observation.value-concept-display", "Observation.value-concept", "Observation.value-date", "Observation.value-quantity-comparator", "Observation.value-quantity-number", "Observation.value-quantity-unit", "Observation.score-total-number", "Observation.bp-systolic-number", "Observation.bp-diastolic-number", "Observation.value-string"];
263
271
  /**
264
272
  * Full persisted claim surface for visible/searchable Vital Signs rows.
265
273
  *
266
274
  * This keeps the claims-first authoring fields but still avoids introducing
267
275
  * fields that only belong to broader Observation families.
268
276
  */
269
- export declare const ObservationVitalSignsClaimsList: readonly ["Observation.category", "Observation.code-system", "Observation.code-value", "Observation.code-text", "Observation.code-display", "Observation.code", "Observation.date", "Observation.effectiveDateTime", "Observation.has-member", "Observation.component-tags", "Observation.component-code-values", "Observation.component-names", "Observation.identifier", "Observation.language", "Observation.method", "Observation.note", "Observation.patient", "Observation.status", "Observation.subject", "Observation.value-concept-system", "Observation.value-concept-value", "Observation.value-concept-text", "Observation.value-concept-display", "Observation.value-concept", "Observation.value-date", "Observation.value-quantity-comparator", "Observation.value-quantity-number", "Observation.value-quantity-unit", "Observation.score-total-number", "Observation.bp-systolic-number", "Observation.bp-diastolic-number", "Observation.value-string"];
277
+ export declare const ObservationVitalSignsClaimsList: readonly ["Observation.category", "Observation.code-system", "Observation.code-value", "Observation.code-text", "Observation.code-display", "Observation.code", "Observation.date", "Observation.effective-datetime", "Observation.has-member", "Observation.component-tags", "Observation.component-code-values", "Observation.component-names", "Observation.identifier", "Observation.language", "Observation.method", "Observation.note", "Observation.patient", "Observation.status", "Observation.subject", "Observation.value-concept-system", "Observation.value-concept-value", "Observation.value-concept-text", "Observation.value-concept-display", "Observation.value-concept", "Observation.value-date", "Observation.value-quantity-comparator", "Observation.value-quantity-number", "Observation.value-quantity-unit", "Observation.score-total-number", "Observation.bp-systolic-number", "Observation.bp-diastolic-number", "Observation.value-string"];
270
278
  export declare const ObservationClaimSpecs: ClaimSpec[];
271
279
  /**
272
280
  * Returns `true` when the flat claims represent one visible/searchable vital
@@ -102,6 +102,14 @@ export const ObservationClaim = {
102
102
  ValueQuantityNumber: 'Observation.value-quantity-number',
103
103
  /** Human-readable quantity unit. Example: `/min`, `mmHg`, `Cel`. */
104
104
  ValueQuantityUnit: 'Observation.value-quantity-unit',
105
+ ReferenceRangeLowNumber: 'Observation.reference-range-low-number',
106
+ ReferenceRangeHighNumber: 'Observation.reference-range-high-number',
107
+ ReferenceRangeUnit: 'Observation.reference-range-unit',
108
+ ReferenceRangeText: 'Observation.reference-range-text',
109
+ ComponentCode: 'Observation.component-code',
110
+ ComponentCodeDisplay: 'Observation.component-code-display',
111
+ ComponentValueQuantityNumber: 'Observation.component-value-quantity-number',
112
+ ComponentValueQuantityUnit: 'Observation.component-value-quantity-unit',
105
113
  /** Aggregated score total for composite assessment observations when present. Example: `8`. */
106
114
  ScoreTotalNumber: 'Observation.score-total-number',
107
115
  /** Indexed systolic scalar copied onto the parent blood pressure observation. Example: `120`. */
@@ -112,8 +120,8 @@ export const ObservationClaim = {
112
120
  ValueString: 'Observation.value-string',
113
121
  /** User-authored note. Example: `Measured after resting 5 minutes.` */
114
122
  Note: 'Observation.note',
115
- /** FHIR-compatible explicit `effectiveDateTime` fallback. Example: `2026-06-01T10:00:00Z`. */
116
- EffectiveDateTime: 'Observation.effectiveDateTime',
123
+ /** FHIR-compatible explicit `effectiveDateTime` fallback using the canonical kebab-case claim name. Example: `2026-06-01T10:00:00Z`. */
124
+ EffectiveDateTime: 'Observation.effective-datetime',
117
125
  };
118
126
  /**
119
127
  * Builds the legacy compact `system|code` token from split claims.
@@ -1,6 +1,15 @@
1
1
  import type { ClaimSpec } from './types';
2
2
  /** Canonical claims used to preserve the primary coded PractitionerRole label. */
3
3
  export declare const PractitionerRoleClaim: {
4
+ readonly Identifier: "PractitionerRole.identifier";
5
+ readonly Active: "PractitionerRole.active";
6
+ readonly Practitioner: "PractitionerRole.practitioner";
7
+ readonly Organization: "PractitionerRole.organization";
8
+ readonly Location: "PractitionerRole.location";
9
+ readonly Service: "PractitionerRole.service";
10
+ readonly Specialty: "PractitionerRole.specialty";
11
+ readonly PeriodStart: "PractitionerRole.period-start";
12
+ readonly PeriodEnd: "PractitionerRole.period-end";
4
13
  readonly Code: "PractitionerRole.code";
5
14
  readonly CodeText: "PractitionerRole.code-text";
6
15
  readonly CodeDisplay: "PractitionerRole.code-display";
@@ -1,6 +1,15 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  /** Canonical claims used to preserve the primary coded PractitionerRole label. */
3
3
  export const PractitionerRoleClaim = {
4
+ Identifier: 'PractitionerRole.identifier',
5
+ Active: 'PractitionerRole.active',
6
+ Practitioner: 'PractitionerRole.practitioner',
7
+ Organization: 'PractitionerRole.organization',
8
+ Location: 'PractitionerRole.location',
9
+ Service: 'PractitionerRole.service',
10
+ Specialty: 'PractitionerRole.specialty',
11
+ PeriodStart: 'PractitionerRole.period-start',
12
+ PeriodEnd: 'PractitionerRole.period-end',
4
13
  Code: 'PractitionerRole.code',
5
14
  CodeText: 'PractitionerRole.code-text',
6
15
  CodeDisplay: 'PractitionerRole.code-display',
@@ -1,8 +1,9 @@
1
1
  import type { ClaimSpec } from './types';
2
2
  export declare const RelatedPersonClaim: {
3
- /** @deprecated Prefer `RelatedPerson.identifier.value` for canonical exactness. */
3
+ /** Canonical FHIR API identifier search parameter. */
4
4
  readonly Identifier: "RelatedPerson.identifier";
5
- readonly IdentifierValue: "RelatedPerson.identifier.value";
5
+ /** @deprecated Use `Identifier`; retained as a source-compatible property alias. */
6
+ readonly IdentifierValue: "RelatedPerson.identifier";
6
7
  readonly Active: "RelatedPerson.active";
7
8
  readonly Patient: "RelatedPerson.patient";
8
9
  readonly Relationship: "RelatedPerson.relationship";
@@ -1,9 +1,10 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  // File: src/models/interoperable-claims/related-person-claims.ts
3
3
  export const RelatedPersonClaim = {
4
- /** @deprecated Prefer `RelatedPerson.identifier.value` for canonical exactness. */
4
+ /** Canonical FHIR API identifier search parameter. */
5
5
  Identifier: 'RelatedPerson.identifier',
6
- IdentifierValue: 'RelatedPerson.identifier.value',
6
+ /** @deprecated Use `Identifier`; retained as a source-compatible property alias. */
7
+ IdentifierValue: 'RelatedPerson.identifier',
7
8
  Active: 'RelatedPerson.active',
8
9
  Patient: 'RelatedPerson.patient',
9
10
  Relationship: 'RelatedPerson.relationship',
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export interface ClaimInteroperable {
5
5
  /**
6
- * Key name of the interoperable claim in reverse-DNS (e.g., 'org.hl7.fhir.immunization.vaccine-code').
6
+ * Key name of the interoperable FHIR API claim in reverse-DNS (e.g., 'org.hl7.fhir.api.Immunization.vaccine-code').
7
7
  */
8
8
  name: string;
9
9
  /**
@@ -5,7 +5,9 @@ import { ConditionClaim } from '../models/interoperable-claims/condition-claims.
5
5
  import { DiagnosticReportClaim } from '../models/interoperable-claims/diagnostic-report-claims.js';
6
6
  import { MedicationStatementClaim } from '../models/interoperable-claims/medication-statement-claims.js';
7
7
  import { ClaimConsent } from '../models/consent-rule.js';
8
- import { allergyIntoleranceFlatToFhirR4, appointmentFlatToFhirR4, carePlanFlatToFhirR4, clinicalImpressionFlatToFhirR4, compositionFlatToFhirR4, conditionFlatToFhirR4, consentFlatToFhirR4, convertFhirResourceToClaims, coverageFlatToFhirR4, deviceFlatToFhirR4, documentReferenceFlatToFhirR4, diagnosticReportFlatToFhirR4, encounterFlatToFhirR4, flagFlatToFhirR4, flatClaimsToFhirResource, immunizationFlatToFhirR4, locationFlatToFhirR4, medicationStatementFlatToFhirR4, observationFromFlatToFhirR4, organizationFlatToFhirR4, procedureFlatToFhirR4, practitionerRoleFlatToFhirR4, relatedPersonFlatToFhirR4, } from './clinical-resource-converters.js';
8
+ import { allergyIntoleranceFlatToFhirR4, appointmentResponseFlatToFhirR4, appointmentFlatToFhirR4, carePlanFlatToFhirR4, clinicalImpressionFlatToFhirR4, compositionFlatToFhirR4, conditionFlatToFhirR4, consentFlatToFhirR4, convertFhirResourceToClaims, coverageFlatToFhirR4, deviceFlatToFhirR4, deviceUseStatementFlatToFhirR4, documentReferenceFlatToFhirR4, diagnosticReportFlatToFhirR4, encounterFlatToFhirR4, flagFlatToFhirR4, flatClaimsToFhirResource, immunizationFlatToFhirR4, locationFlatToFhirR4, medicationStatementFlatToFhirR4, observationFromFlatToFhirR4, organizationFlatToFhirR4, procedureFlatToFhirR4, practitionerRoleFlatToFhirR4, relatedPersonFlatToFhirR4, } from './clinical-resource-converters.js';
9
+ /** Version-independent FHIR SearchParameter claim context. */
10
+ const FHIR_API_CLAIMS_CONTEXT = 'org.hl7.fhir.api';
9
11
  function asTrimmedString(value) {
10
12
  if (value === undefined || value === null)
11
13
  return '';
@@ -22,8 +24,7 @@ export function getSimpleClaimAttributeName(key) {
22
24
  if (!value)
23
25
  return '';
24
26
  const knownPrefixes = [
25
- 'org.hl7.fhir.r4.',
26
- 'org.hl7.fhir.api.',
27
+ `${FHIR_API_CLAIMS_CONTEXT}.`,
27
28
  ];
28
29
  for (const prefix of knownPrefixes) {
29
30
  if (value.startsWith(prefix)) {
@@ -39,19 +40,13 @@ export function extractFlatClaimValue(record, key) {
39
40
  const direct = record[normalizedKey];
40
41
  if (typeof direct === 'string' && direct.trim())
41
42
  return direct.trim();
42
- const contextualizedR4 = record[`org.hl7.fhir.r4.${normalizedKey}`];
43
- if (typeof contextualizedR4 === 'string' && contextualizedR4.trim())
44
- return contextualizedR4.trim();
45
- const contextualizedApi = record[`org.hl7.fhir.api.${normalizedKey}`];
43
+ const contextualizedApi = record[`${FHIR_API_CLAIMS_CONTEXT}.${normalizedKey}`];
46
44
  if (typeof contextualizedApi === 'string' && contextualizedApi.trim())
47
45
  return contextualizedApi.trim();
48
46
  const nested = record?.meta?.claims?.[normalizedKey];
49
47
  if (typeof nested === 'string' && nested.trim())
50
48
  return nested.trim();
51
- const nestedR4 = record?.meta?.claims?.[`org.hl7.fhir.r4.${normalizedKey}`];
52
- if (typeof nestedR4 === 'string' && nestedR4.trim())
53
- return nestedR4.trim();
54
- const nestedApi = record?.meta?.claims?.[`org.hl7.fhir.api.${normalizedKey}`];
49
+ const nestedApi = record?.meta?.claims?.[`${FHIR_API_CLAIMS_CONTEXT}.${normalizedKey}`];
55
50
  if (typeof nestedApi === 'string' && nestedApi.trim())
56
51
  return nestedApi.trim();
57
52
  return '';
@@ -59,12 +54,33 @@ export function extractFlatClaimValue(record, key) {
59
54
  function claimsToFlatStrings(claims) {
60
55
  const out = {};
61
56
  for (const [key, value] of Object.entries(claims || {})) {
62
- if (value === undefined || value === null)
57
+ if (value === undefined || value === null || key === '@context')
58
+ continue;
59
+ const canonicalKey = getSimpleClaimAttributeName(key);
60
+ if (canonicalKey === key)
61
+ continue;
62
+ out[canonicalKey] = typeof value === 'string' ? value : String(value);
63
+ }
64
+ for (const [key, value] of Object.entries(claims || {})) {
65
+ if (value === undefined || value === null || key === '@context')
66
+ continue;
67
+ const canonicalKey = getSimpleClaimAttributeName(key);
68
+ if (canonicalKey !== key)
63
69
  continue;
64
70
  out[key] = typeof value === 'string' ? value : String(value);
65
71
  }
66
72
  return out;
67
73
  }
74
+ /** Rejects version-specific FHIR resource contexts from the claims contract. */
75
+ function assertFhirApiClaimsContext(claims) {
76
+ const context = asTrimmedString(claims?.['@context']);
77
+ const hasVersionSpecificContext = context.startsWith('org.hl7.fhir.')
78
+ && context !== FHIR_API_CLAIMS_CONTEXT;
79
+ const hasVersionSpecificExpandedKey = Object.keys(claims || {}).some((key) => key.startsWith('org.hl7.fhir.') && !key.startsWith(`${FHIR_API_CLAIMS_CONTEXT}.`));
80
+ if (hasVersionSpecificContext || hasVersionSpecificExpandedKey) {
81
+ throw new Error(`FHIR claims require @context ${FHIR_API_CLAIMS_CONTEXT}. Version-specific FHIR resource contexts are not claim namespaces.`);
82
+ }
83
+ }
68
84
  function ensureClaimsIdentifier(claims, resource) {
69
85
  const resourceType = asTrimmedString(resource?.resourceType);
70
86
  const identifierKey = resourceType ? `${resourceType}.identifier` : '';
@@ -202,7 +218,7 @@ export function prepareBundleDocumentForSubject(bundle, subjectDid, options = {}
202
218
  if (!resource || typeof resource !== 'object')
203
219
  continue;
204
220
  replacePatientSubjectReference(resource, normalizedSubjectDid);
205
- const generated = convertFhirResourceToClaims(resource, options.context || 'org.hl7.fhir.r4');
221
+ const generated = convertFhirResourceToClaims(resource, options.context || FHIR_API_CLAIMS_CONTEXT);
206
222
  const existing = resource?.meta?.claims;
207
223
  const claims = {
208
224
  ...generated,
@@ -324,7 +340,7 @@ export function detectClaimsResourceType(claims) {
324
340
  return ResourceTypesFhirR4.DocumentReference;
325
341
  const firstContextualized = keys
326
342
  .map((key) => getSimpleClaimAttributeName(key))
327
- .find((key) => key.includes('.'));
343
+ .find((key) => key.includes('.') && !key.startsWith(`${ResourceTypesFhirR4.Composition}.`));
328
344
  if (firstContextualized) {
329
345
  return firstContextualized.split('.')[0];
330
346
  }
@@ -332,10 +348,11 @@ export function detectClaimsResourceType(claims) {
332
348
  }
333
349
  export function convertClaimsToFhirResource(claims, version = 'r4') {
334
350
  void version;
351
+ assertFhirApiClaimsContext(claims);
335
352
  const flatClaims = claimsToFlatStrings(claims);
336
353
  const resourceType = detectClaimsResourceType(claims);
337
354
  const resource = convertClaimsToFhirResourceByType(flatClaims, resourceType);
338
- const language = resourceType ? asTrimmedString(claims[`${resourceType}.language`]) : '';
355
+ const language = resourceType ? asTrimmedString(flatClaims[`${resourceType}.language`]) : '';
339
356
  return language ? { ...resource, language } : resource;
340
357
  }
341
358
  function convertClaimsToFhirResourceByType(flatClaims, resourceType) {
@@ -348,6 +365,8 @@ function convertClaimsToFhirResourceByType(flatClaims, resourceType) {
348
365
  return conditionFlatToFhirR4(flatClaims);
349
366
  case ResourceTypesFhirR4.DocumentReference:
350
367
  return documentReferenceFlatToFhirR4(flatClaims);
368
+ case ResourceTypesFhirR4.DeviceUseStatement:
369
+ return deviceUseStatementFlatToFhirR4(flatClaims);
351
370
  case 'Immunization':
352
371
  return immunizationFlatToFhirR4(flatClaims);
353
372
  case 'Location':
@@ -370,6 +389,8 @@ function convertClaimsToFhirResourceByType(flatClaims, resourceType) {
370
389
  return compositionFlatToFhirR4(flatClaims);
371
390
  case 'Appointment':
372
391
  return appointmentFlatToFhirR4(flatClaims);
392
+ case 'AppointmentResponse':
393
+ return appointmentResponseFlatToFhirR4(flatClaims);
373
394
  case 'Encounter':
374
395
  return encounterFlatToFhirR4(flatClaims);
375
396
  case 'RelatedPerson':
@@ -397,7 +418,7 @@ function convertClaimsToFhirResourceByType(flatClaims, resourceType) {
397
418
  * not yet part of the clinical claims-first pipeline:
398
419
  * `Composition`, `Patient`, `Practitioner`, `PractitionerRole`.
399
420
  */
400
- export function extractBundleDocumentClaimsList(bundle, context = 'org.hl7.fhir.r4') {
421
+ export function extractBundleDocumentClaimsList(bundle, context = 'org.hl7.fhir.api') {
401
422
  const entries = Array.isArray(bundle?.entry) ? bundle.entry : [];
402
423
  const ignoredResourceTypes = new Set([
403
424
  ResourceTypesFhirR4.Composition,
@@ -75,6 +75,19 @@ export declare class BundleEntryEditor {
75
75
  hasClaim(key: string): boolean;
76
76
  /** Writes one claim on this entry. */
77
77
  setClaim(key: string, value: unknown): this;
78
+ /**
79
+ * Writes one viewer/editor field using the canonical short FHIR API claim.
80
+ * Expanded `org.hl7.fhir.api.*` input is normalized; versioned FHIR claim
81
+ * namespaces and structural/camelCase paths are rejected.
82
+ */
83
+ setFhirApiClaim(key: string, value: unknown): this;
84
+ /** Reads one short or expanded FHIR API claim through the canonical short key. */
85
+ getFhirApiClaim(key: string): unknown;
86
+ /** Applies a complete field collection returned by a clinical card view. */
87
+ setFhirApiClaimFields(fields: readonly Readonly<{
88
+ claim: string;
89
+ value: unknown;
90
+ }>[]): this;
78
91
  /** Appends one claim value on this entry. */
79
92
  addClaim(key: string, value: unknown): this;
80
93
  /** Removes one claim from this entry. */
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
8
8
  import { BundleEditableResourceTypes, } from '../models/bundle-editor-types.js';
9
+ import { normalizeFhirApiClaimKey } from './fhir-api-claim-helpers.js';
9
10
  import { EmployeeResourceTypes, } from './employee.js';
10
11
  import { cloneClaimValue, normalizeOptionalIdentifier } from './bundle-editor-helpers.js';
11
12
  import { createRegisteredBundleEntryEditor } from './bundle-editor-registry.js';
@@ -241,6 +242,23 @@ export class BundleEntryEditor {
241
242
  };
242
243
  return this;
243
244
  }
245
+ /**
246
+ * Writes one viewer/editor field using the canonical short FHIR API claim.
247
+ * Expanded `org.hl7.fhir.api.*` input is normalized; versioned FHIR claim
248
+ * namespaces and structural/camelCase paths are rejected.
249
+ */
250
+ setFhirApiClaim(key, value) {
251
+ return this.setClaim(normalizeFhirApiClaimKey(key), value);
252
+ }
253
+ /** Reads one short or expanded FHIR API claim through the canonical short key. */
254
+ getFhirApiClaim(key) {
255
+ return this.getClaim(normalizeFhirApiClaimKey(key));
256
+ }
257
+ /** Applies a complete field collection returned by a clinical card view. */
258
+ setFhirApiClaimFields(fields) {
259
+ fields.forEach((field) => this.setFhirApiClaim(field.claim, field.value));
260
+ return this;
261
+ }
244
262
  /** Appends one claim value on this entry. */
245
263
  addClaim(key, value) {
246
264
  const normalizedKey = String(key).trim();
@@ -99,12 +99,14 @@ export const practitionerRoleFhirToFlat = practitionerRoleFhirR4ToFlat;
99
99
  * resource-specific converter exists. Unsupported resources fall back to the
100
100
  * generic structural flattening.
101
101
  */
102
- export function convertFhirResourceToClaims(resource, context = 'org.hl7.fhir.r4') {
102
+ export function convertFhirResourceToClaims(resource, context = 'org.hl7.fhir.api') {
103
103
  const claims = convertFhirResourceToClaimsByType(resource, context);
104
104
  const language = typeof resource.language === 'string' ? resource.language.trim() : '';
105
- return language
106
- ? { ...claims, [`${resource.resourceType}.language`]: language }
107
- : claims;
105
+ return {
106
+ '@context': context,
107
+ ...claims,
108
+ ...(language ? { [`${resource.resourceType}.language`]: language } : {}),
109
+ };
108
110
  }
109
111
  function convertFhirResourceToClaimsByType(resource, context) {
110
112
  switch (resource.resourceType) {
@@ -20,9 +20,54 @@ export type ClinicalResourceCommonView = Readonly<{
20
20
  export type ClinicalResourceCardView = Readonly<{
21
21
  title: string;
22
22
  resourceType: string;
23
+ /** Stable business identifier used to reconcile repeated readbacks. */
24
+ identifier?: string;
23
25
  date?: string;
26
+ periodStart?: string;
27
+ periodEnd?: string;
24
28
  fullUrl?: string;
25
29
  actorsCount: number;
30
+ /** Resource status resolved from canonical claims first, then native FHIR. */
31
+ status?: string;
32
+ /** Observation scalar value for result cards. */
33
+ value?: string | number;
34
+ /** Observation quantity unit for result cards. */
35
+ unit?: string;
36
+ /** Immunization lot number. */
37
+ lotNumber?: string;
38
+ /** Immunization dose sequence. */
39
+ doseSequence?: string;
40
+ /** AllergyIntolerance criticality. */
41
+ criticality?: string;
42
+ /** AllergyIntolerance onset date/time. */
43
+ onsetDateTime?: string;
44
+ /** MedicationStatement human-readable dosage instruction. */
45
+ dosageInstruction?: string;
46
+ category?: string;
47
+ severity?: string;
48
+ recordedDate?: string;
49
+ source?: string;
50
+ route?: string;
51
+ site?: string;
52
+ doseQuantityValue?: number;
53
+ doseQuantityUnit?: string;
54
+ referenceRangeText?: string;
55
+ description?: string;
56
+ /**
57
+ * Complete editable field surface resolved from `resource.meta.claims`.
58
+ *
59
+ * `claim` always uses the short, version-independent FHIR API form
60
+ * (`<ResourceType>.<search-param>`). Consumers can therefore render and
61
+ * hydrate fields that do not yet have a dedicated card convenience member.
62
+ */
63
+ fields: readonly ClinicalResourceClaimFieldView[];
64
+ }>;
65
+ export type ClinicalResourceClaimFieldView = Readonly<{
66
+ /** Canonical short FHIR API claim, for example `Immunization.lot-number`. */
67
+ claim: string;
68
+ /** Concrete FHIR API SearchParameter name, for example `lot-number`. */
69
+ parameter: string;
70
+ value: unknown;
26
71
  }>;
27
72
  export type ClinicalTerminologyTranslationInput = Readonly<{
28
73
  resourceType: string;
@@ -110,6 +155,16 @@ export declare function toClinicalResourceCommonViews(bundle: ClinicalResourceBu
110
155
  * Maps one bundle entry to a minimal card view for section counters/list cards.
111
156
  */
112
157
  export declare function toClinicalResourceCardView(entry: ClinicalResourceEntryLike, options?: ClinicalResourceDisplayOptions): ClinicalResourceCardView;
158
+ /**
159
+ * Normalizes all clinical `meta.claims` into the short FHIR API vocabulary
160
+ * consumed by generic clinical viewers and editors.
161
+ *
162
+ * Short claims take precedence over their expanded
163
+ * `org.hl7.fhir.api.<ResourceType>.<search-param>` equivalent. Versioned
164
+ * `org.hl7.fhir.<version>.*` namespaces are rejected because they are native
165
+ * FHIR representation namespaces, never claims vocabularies.
166
+ */
167
+ export declare function toClinicalResourceClaimFieldViews(claims: ClinicalViewClaims): ClinicalResourceClaimFieldView[];
113
168
  /**
114
169
  * Maps all entries from a Bundle into minimal card views.
115
170
  */
@@ -14,6 +14,7 @@ import { MedicationStatementClaim, MedicationStatementClaimsFhirApi, MedicationS
14
14
  import { ObservationClaim } from '../models/interoperable-claims/observation-claims.js';
15
15
  import { PractitionerRoleClaim } from '../models/interoperable-claims/practitioner-role-claims.js';
16
16
  import { ProcedureClaim } from '../models/interoperable-claims/procedure-claims.js';
17
+ import { normalizeFhirApiClaimKey } from './fhir-api-claim-helpers.js';
17
18
  const CONSENT_ACTOR_REFERENCE_CLAIM = 'Consent.actor-reference';
18
19
  const GENERIC_CREATOR_CLAIM_SUFFIX = '.creator';
19
20
  const GENERIC_PERFORMER_CLAIM_SUFFIX = '.performer';
@@ -49,14 +50,112 @@ export function toClinicalResourceCommonViews(bundle, options = {}) {
49
50
  */
50
51
  export function toClinicalResourceCardView(entry, options = {}) {
51
52
  const common = toClinicalResourceCommonView(entry, options);
53
+ const resource = entry.resource;
54
+ const status = resolveCardStatus(common.resourceType, common.claims, resource);
55
+ const value = resolveCardValue(common.resourceType, common.claims, resource);
56
+ const unit = resolveCardUnit(common.resourceType, common.claims, resource);
57
+ const lotNumber = readCanonicalClaimValue(common.claims, ImmunizationClaim.LotNumber)
58
+ || trimValue(resource?.lotNumber);
59
+ const doseSequence = readCanonicalClaimValue(common.claims, ImmunizationClaim.DoseSequence)
60
+ || trimValue(asRecord(asArray(resource?.protocolApplied)[0]).doseNumberString);
61
+ const criticality = readCanonicalClaimValue(common.claims, AllergyIntoleranceClaim.Criticality)
62
+ || trimValue(resource?.criticality);
63
+ const onsetDateTime = readCanonicalClaimValue(common.claims, AllergyIntoleranceClaim.OnsetDateTime)
64
+ || trimValue(resource?.onsetDateTime);
65
+ const dosageInstruction = readCanonicalClaimValue(common.claims, MedicationStatementClaim.DosageInstruction)
66
+ || trimValue(asRecord(asArray(resource?.dosage)[0]).text);
67
+ const fields = toClinicalResourceClaimFieldViews(common.claims);
68
+ const category = readCanonicalClaimValue(common.claims, `${common.resourceType}.category`);
69
+ const severity = readCanonicalClaimValue(common.claims, `${common.resourceType}.severity`);
70
+ const recordedDate = readCanonicalClaimValue(common.claims, ConditionClaim.RecordedDate);
71
+ const source = readCanonicalClaimValue(common.claims, MedicationStatementClaim.Source);
72
+ const route = readCanonicalClaimValue(common.claims, ImmunizationClaim.Route);
73
+ const site = readCanonicalClaimValue(common.claims, ImmunizationClaim.Site);
74
+ const doseQuantityValueRaw = readCanonicalClaimValue(common.claims, MedicationStatementClaim.DoseQuantityValue);
75
+ const doseQuantityValue = doseQuantityValueRaw === undefined ? undefined : Number(doseQuantityValueRaw);
76
+ const doseQuantityUnit = readCanonicalClaimValue(common.claims, MedicationStatementClaim.DoseQuantityUnit);
77
+ const referenceRangeText = readCanonicalClaimValue(common.claims, ObservationClaim.ReferenceRangeText);
78
+ const description = readCanonicalClaimValue(common.claims, CarePlanClaim.Description);
52
79
  return {
53
80
  title: common.title,
54
81
  resourceType: common.resourceType,
82
+ ...(common.identifier ? { identifier: common.identifier } : {}),
55
83
  date: common.date,
84
+ ...(common.periodStart ? { periodStart: common.periodStart } : {}),
85
+ ...(common.periodEnd ? { periodEnd: common.periodEnd } : {}),
56
86
  fullUrl: common.fullUrl,
57
87
  actorsCount: common.actors.length,
88
+ fields,
89
+ ...(status ? { status } : {}),
90
+ ...(value !== undefined ? { value } : {}),
91
+ ...(unit ? { unit } : {}),
92
+ ...(lotNumber ? { lotNumber } : {}),
93
+ ...(doseSequence ? { doseSequence } : {}),
94
+ ...(criticality ? { criticality } : {}),
95
+ ...(onsetDateTime ? { onsetDateTime } : {}),
96
+ ...(dosageInstruction ? { dosageInstruction } : {}),
97
+ ...(category ? { category } : {}),
98
+ ...(severity ? { severity } : {}),
99
+ ...(recordedDate ? { recordedDate } : {}),
100
+ ...(source ? { source } : {}),
101
+ ...(route ? { route } : {}),
102
+ ...(site ? { site } : {}),
103
+ ...(doseQuantityValue !== undefined && Number.isFinite(doseQuantityValue) ? { doseQuantityValue } : {}),
104
+ ...(doseQuantityUnit ? { doseQuantityUnit } : {}),
105
+ ...(referenceRangeText ? { referenceRangeText } : {}),
106
+ ...(description ? { description } : {}),
58
107
  };
59
108
  }
109
+ /**
110
+ * Normalizes all clinical `meta.claims` into the short FHIR API vocabulary
111
+ * consumed by generic clinical viewers and editors.
112
+ *
113
+ * Short claims take precedence over their expanded
114
+ * `org.hl7.fhir.api.<ResourceType>.<search-param>` equivalent. Versioned
115
+ * `org.hl7.fhir.<version>.*` namespaces are rejected because they are native
116
+ * FHIR representation namespaces, never claims vocabularies.
117
+ */
118
+ export function toClinicalResourceClaimFieldViews(claims) {
119
+ const fields = new Map();
120
+ const entries = Object.entries(claims || {});
121
+ for (const [rawClaim, value] of entries) {
122
+ if (rawClaim.startsWith('@') || value === undefined)
123
+ continue;
124
+ if (rawClaim.startsWith('org.hl7.fhir.') && !rawClaim.startsWith('org.hl7.fhir.api.')) {
125
+ throw new Error(`FHIR meta.claims must use org.hl7.fhir.api, not version-specific key: ${rawClaim}`);
126
+ }
127
+ if (!rawClaim.startsWith('org.hl7.fhir.api.') && !/^[A-Z][A-Za-z0-9]+\./.test(rawClaim))
128
+ continue;
129
+ const claim = normalizeFhirApiClaimKey(rawClaim);
130
+ const separator = claim.indexOf('.');
131
+ if (separator <= 0 || separator === claim.length - 1)
132
+ continue;
133
+ if (!fields.has(claim)) {
134
+ fields.set(claim, {
135
+ claim,
136
+ parameter: claim.slice(separator + 1),
137
+ value,
138
+ });
139
+ }
140
+ }
141
+ // Apply canonical short keys last so they win over expanded aliases even
142
+ // when the expanded property appeared later in insertion order.
143
+ for (const [claim, value] of entries) {
144
+ if (claim.startsWith('@') || claim.startsWith('org.hl7.fhir.') || value === undefined)
145
+ continue;
146
+ if (!/^[A-Z][A-Za-z0-9]+\./.test(claim))
147
+ continue;
148
+ const separator = claim.indexOf('.');
149
+ if (separator <= 0 || separator === claim.length - 1)
150
+ continue;
151
+ fields.set(claim, {
152
+ claim,
153
+ parameter: claim.slice(separator + 1),
154
+ value,
155
+ });
156
+ }
157
+ return [...fields.values()].sort((left, right) => left.claim.localeCompare(right.claim));
158
+ }
60
159
  /**
61
160
  * Maps all entries from a Bundle into minimal card views.
62
161
  */
@@ -196,6 +295,42 @@ export function getNarrative(resource) {
196
295
  source: 'derived-from-claims',
197
296
  };
198
297
  }
298
+ function resolveCardStatus(resourceType, claims, resource) {
299
+ const claimsStatus = resourceType === ResourceTypesFhirR4.AllergyIntolerance
300
+ ? readCanonicalClaimValue(claims, AllergyIntoleranceClaim.ClinicalStatus)
301
+ : readCanonicalClaimValue(claims, `${resourceType}.status`);
302
+ if (claimsStatus)
303
+ return claimsStatus;
304
+ const nativeStatus = trimValue(resource?.status);
305
+ if (nativeStatus)
306
+ return nativeStatus;
307
+ return trimValue(asRecord(asArray(asRecord(resource?.clinicalStatus).coding)[0]).code) || undefined;
308
+ }
309
+ function resolveCardValue(resourceType, claims, resource) {
310
+ if (resourceType !== ResourceTypesFhirR4.Observation)
311
+ return undefined;
312
+ const claimNumber = readCanonicalClaimValue(claims, ObservationClaim.ValueQuantityNumber);
313
+ if (claimNumber !== undefined) {
314
+ const numeric = Number(claimNumber);
315
+ if (Number.isFinite(numeric))
316
+ return numeric;
317
+ }
318
+ const claimString = readCanonicalClaimValue(claims, ObservationClaim.ValueString);
319
+ if (claimString)
320
+ return claimString;
321
+ const nativeNumber = asRecord(resource?.valueQuantity).value;
322
+ if (typeof nativeNumber === 'number' && Number.isFinite(nativeNumber))
323
+ return nativeNumber;
324
+ return trimValue(resource?.valueString) || undefined;
325
+ }
326
+ function resolveCardUnit(resourceType, claims, resource) {
327
+ if (resourceType !== ResourceTypesFhirR4.Observation)
328
+ return undefined;
329
+ return readCanonicalClaimValue(claims, ObservationClaim.ValueQuantityUnit)
330
+ || trimValue(asRecord(resource?.valueQuantity).unit)
331
+ || trimValue(asRecord(resource?.valueQuantity).code)
332
+ || undefined;
333
+ }
199
334
  function readClaims(entry) {
200
335
  const resourceClaims = asRecord(entry?.resource?.meta?.claims);
201
336
  const legacyClaims = asRecord(entry?.meta?.claims);
@@ -397,7 +532,8 @@ function resolveIdentifier(resourceType, claims, resource) {
397
532
  AllergyIntoleranceClaimsFhirApi.Identifier,
398
533
  ]) || resolveFhirIdentifier(resource);
399
534
  }
400
- return resolveFhirIdentifier(resource);
535
+ return readCanonicalClaimValue(claims, `${resourceType}.identifier`)
536
+ || resolveFhirIdentifier(resource);
401
537
  }
402
538
  function resolveDate(resourceType, claims, resource) {
403
539
  if (resourceType === ResourceTypesFhirR4.Communication) {
@@ -67,7 +67,7 @@ export function buildBundleDocumentRequestCommunicationClaims(input) {
67
67
  : `Request ${documentTypeDescriptor.id} Bundle search with sections: ${sections.join(', ')}`)).trim();
68
68
  const noteText = String(input.noteText || text).trim();
69
69
  const claims = {
70
- '@context': 'org.hl7.fhir.r4',
70
+ '@context': 'org.hl7.fhir.api',
71
71
  [CommunicationClaim.Identifier]: communicationIdentifier,
72
72
  [CommunicationClaim.Status]: String(input.status || 'completed').trim(),
73
73
  [CommunicationClaim.Category]: CommunicationCategoryCodes.Notification.claim,
@@ -121,7 +121,7 @@ export function extractCommunicationClaimsFromResourceFhirR4(resource, options =
121
121
  return { ...existingClaims };
122
122
  }
123
123
  const claims = {};
124
- claims['@context'] = 'org.hl7.fhir.r4';
124
+ claims['@context'] = 'org.hl7.fhir.api';
125
125
  const identifierValue = resource?.identifier?.[0]?.value;
126
126
  const status = resource?.status;
127
127
  const sent = resource?.sent;
@@ -0,0 +1,2 @@
1
+ /** Returns the canonical short FHIR API claim or throws for another vocabulary. */
2
+ export declare function normalizeFhirApiClaimKey(claimKey: string): string;
@@ -0,0 +1,16 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ const FHIR_API_PREFIX = 'org.hl7.fhir.api.';
3
+ const FHIR_VERSIONED_PREFIX = 'org.hl7.fhir.';
4
+ const SHORT_FHIR_API_CLAIM = /^[A-Z][A-Za-z0-9]+\.[a-z0-9]+(?:-[a-z0-9]+)*$/;
5
+ /** Returns the canonical short FHIR API claim or throws for another vocabulary. */
6
+ export function normalizeFhirApiClaimKey(claimKey) {
7
+ const raw = String(claimKey || '').trim();
8
+ if (raw.startsWith(FHIR_VERSIONED_PREFIX) && !raw.startsWith(FHIR_API_PREFIX)) {
9
+ throw new Error(`FHIR claims must use org.hl7.fhir.api, not version-specific key: ${raw}`);
10
+ }
11
+ const normalized = raw.startsWith(FHIR_API_PREFIX) ? raw.slice(FHIR_API_PREFIX.length) : raw;
12
+ if (!SHORT_FHIR_API_CLAIM.test(normalized)) {
13
+ throw new Error(`Invalid FHIR API claim key: ${raw}`);
14
+ }
15
+ return normalized;
16
+ }