gdc-common-utils-ts 2.0.5 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -138,6 +138,8 @@ read first:
138
138
 
139
139
  - [`docs/101-COMMUNICATION_LAYERING.md`](docs/101-COMMUNICATION_LAYERING.md)
140
140
  - [`docs/101-BUNDLE_EDITOR_READER.md`](docs/101-BUNDLE_EDITOR_READER.md)
141
+ - [`docs/101-CLINICAL-IPS.md`](docs/101-CLINICAL-IPS.md)
142
+ - [`docs/REFERENCE-CLINICAL-IPS-API.md`](docs/REFERENCE-CLINICAL-IPS-API.md)
141
143
  - [`docs/101-CONSENT_PERMISSION_TEMPLATES.md`](docs/101-CONSENT_PERMISSION_TEMPLATES.md)
142
144
 
143
145
  ## Install
@@ -270,6 +272,12 @@ import { JweObject, JwtCompactParts } from 'gdc-common-utils-ts/models';
270
272
  planning
271
273
  - [docs/101-IPS_BUNDLE.md](docs/101-IPS_BUNDLE.md)
272
274
  - canonical 101 for requesting IPS, editing IPS-style bundles in `Communication.content-attachment-data`, and reading resources by section
275
+ - [docs/101-CLINICAL-IPS.md](docs/101-CLINICAL-IPS.md)
276
+ - shortest high-level onboarding for `ipsBundleReader`, section summaries,
277
+ family queries, and UI-ready narrative helpers
278
+ - [docs/REFERENCE-CLINICAL-IPS-API.md](docs/REFERENCE-CLINICAL-IPS-API.md)
279
+ - canonical claim/method matrix with `TODO` coverage for missing typed
280
+ `get...` / `set...` helpers
273
281
 
274
282
  ## Dataspace Protocol And Discovery
275
283
 
@@ -0,0 +1,83 @@
1
+ export declare const AllergyIntoleranceClinicalStatuses: Readonly<{
2
+ readonly Active: "active";
3
+ readonly Inactive: "inactive";
4
+ readonly Resolved: "resolved";
5
+ }>;
6
+ export type AllergyIntoleranceClinicalStatus = typeof AllergyIntoleranceClinicalStatuses[keyof typeof AllergyIntoleranceClinicalStatuses];
7
+ export declare const AllergyIntoleranceVerificationStatuses: Readonly<{
8
+ readonly Unconfirmed: "unconfirmed";
9
+ readonly Confirmed: "confirmed";
10
+ readonly Refuted: "refuted";
11
+ readonly EnteredInError: "entered-in-error";
12
+ }>;
13
+ export type AllergyIntoleranceVerificationStatus = typeof AllergyIntoleranceVerificationStatuses[keyof typeof AllergyIntoleranceVerificationStatuses];
14
+ export declare const ConditionClinicalStatuses: Readonly<{
15
+ readonly Active: "active";
16
+ readonly Recurrence: "recurrence";
17
+ readonly Relapse: "relapse";
18
+ readonly Inactive: "inactive";
19
+ readonly Remission: "remission";
20
+ readonly Resolved: "resolved";
21
+ }>;
22
+ export type ConditionClinicalStatus = typeof ConditionClinicalStatuses[keyof typeof ConditionClinicalStatuses];
23
+ export declare const ConditionVerificationStatuses: Readonly<{
24
+ readonly Unconfirmed: "unconfirmed";
25
+ readonly Provisional: "provisional";
26
+ readonly Differential: "differential";
27
+ readonly Confirmed: "confirmed";
28
+ readonly Refuted: "refuted";
29
+ readonly EnteredInError: "entered-in-error";
30
+ }>;
31
+ export type ConditionVerificationStatus = typeof ConditionVerificationStatuses[keyof typeof ConditionVerificationStatuses];
32
+ export declare const MedicationStatementStatuses: Readonly<{
33
+ readonly Active: "active";
34
+ readonly Completed: "completed";
35
+ readonly EnteredInError: "entered-in-error";
36
+ readonly Intended: "intended";
37
+ readonly Stopped: "stopped";
38
+ readonly OnHold: "on-hold";
39
+ readonly Unknown: "unknown";
40
+ readonly NotTaken: "not-taken";
41
+ }>;
42
+ export type MedicationStatementStatus = typeof MedicationStatementStatuses[keyof typeof MedicationStatementStatuses];
43
+ export declare const ImmunizationStatuses: Readonly<{
44
+ readonly Completed: "completed";
45
+ readonly EnteredInError: "entered-in-error";
46
+ readonly NotDone: "not-done";
47
+ }>;
48
+ export type ImmunizationStatus = typeof ImmunizationStatuses[keyof typeof ImmunizationStatuses];
49
+ export declare const ProcedureStatuses: Readonly<{
50
+ readonly Preparation: "preparation";
51
+ readonly InProgress: "in-progress";
52
+ readonly NotDone: "not-done";
53
+ readonly OnHold: "on-hold";
54
+ readonly Stopped: "stopped";
55
+ readonly Completed: "completed";
56
+ readonly EnteredInError: "entered-in-error";
57
+ readonly Unknown: "unknown";
58
+ }>;
59
+ export type ProcedureStatus = typeof ProcedureStatuses[keyof typeof ProcedureStatuses];
60
+ export declare const DiagnosticReportStatuses: Readonly<{
61
+ readonly Registered: "registered";
62
+ readonly Partial: "partial";
63
+ readonly Preliminary: "preliminary";
64
+ readonly Final: "final";
65
+ readonly Amended: "amended";
66
+ readonly Corrected: "corrected";
67
+ readonly Appended: "appended";
68
+ readonly Cancelled: "cancelled";
69
+ readonly EnteredInError: "entered-in-error";
70
+ readonly Unknown: "unknown";
71
+ }>;
72
+ export type DiagnosticReportStatus = typeof DiagnosticReportStatuses[keyof typeof DiagnosticReportStatuses];
73
+ export declare const ObservationStatuses: Readonly<{
74
+ readonly Registered: "registered";
75
+ readonly Preliminary: "preliminary";
76
+ readonly Final: "final";
77
+ readonly Amended: "amended";
78
+ readonly Corrected: "corrected";
79
+ readonly Cancelled: "cancelled";
80
+ readonly EnteredInError: "entered-in-error";
81
+ readonly Unknown: "unknown";
82
+ }>;
83
+ export type ObservationStatus = typeof ObservationStatuses[keyof typeof ObservationStatuses];
@@ -0,0 +1,74 @@
1
+ export const AllergyIntoleranceClinicalStatuses = Object.freeze({
2
+ Active: 'active',
3
+ Inactive: 'inactive',
4
+ Resolved: 'resolved',
5
+ });
6
+ export const AllergyIntoleranceVerificationStatuses = Object.freeze({
7
+ Unconfirmed: 'unconfirmed',
8
+ Confirmed: 'confirmed',
9
+ Refuted: 'refuted',
10
+ EnteredInError: 'entered-in-error',
11
+ });
12
+ export const ConditionClinicalStatuses = Object.freeze({
13
+ Active: 'active',
14
+ Recurrence: 'recurrence',
15
+ Relapse: 'relapse',
16
+ Inactive: 'inactive',
17
+ Remission: 'remission',
18
+ Resolved: 'resolved',
19
+ });
20
+ export const ConditionVerificationStatuses = Object.freeze({
21
+ Unconfirmed: 'unconfirmed',
22
+ Provisional: 'provisional',
23
+ Differential: 'differential',
24
+ Confirmed: 'confirmed',
25
+ Refuted: 'refuted',
26
+ EnteredInError: 'entered-in-error',
27
+ });
28
+ export const MedicationStatementStatuses = Object.freeze({
29
+ Active: 'active',
30
+ Completed: 'completed',
31
+ EnteredInError: 'entered-in-error',
32
+ Intended: 'intended',
33
+ Stopped: 'stopped',
34
+ OnHold: 'on-hold',
35
+ Unknown: 'unknown',
36
+ NotTaken: 'not-taken',
37
+ });
38
+ export const ImmunizationStatuses = Object.freeze({
39
+ Completed: 'completed',
40
+ EnteredInError: 'entered-in-error',
41
+ NotDone: 'not-done',
42
+ });
43
+ export const ProcedureStatuses = Object.freeze({
44
+ Preparation: 'preparation',
45
+ InProgress: 'in-progress',
46
+ NotDone: 'not-done',
47
+ OnHold: 'on-hold',
48
+ Stopped: 'stopped',
49
+ Completed: 'completed',
50
+ EnteredInError: 'entered-in-error',
51
+ Unknown: 'unknown',
52
+ });
53
+ export const DiagnosticReportStatuses = Object.freeze({
54
+ Registered: 'registered',
55
+ Partial: 'partial',
56
+ Preliminary: 'preliminary',
57
+ Final: 'final',
58
+ Amended: 'amended',
59
+ Corrected: 'corrected',
60
+ Appended: 'appended',
61
+ Cancelled: 'cancelled',
62
+ EnteredInError: 'entered-in-error',
63
+ Unknown: 'unknown',
64
+ });
65
+ export const ObservationStatuses = Object.freeze({
66
+ Registered: 'registered',
67
+ Preliminary: 'preliminary',
68
+ Final: 'final',
69
+ Amended: 'amended',
70
+ Corrected: 'corrected',
71
+ Cancelled: 'cancelled',
72
+ EnteredInError: 'entered-in-error',
73
+ Unknown: 'unknown',
74
+ });
@@ -1,5 +1,6 @@
1
1
  export * from './actor-session';
2
2
  export * from './communication';
3
+ export * from './clinical-statuses';
3
4
  export * from './cryptography';
4
5
  export * from './data-capabilities';
5
6
  export * from './dataspace-discovery';
@@ -1,5 +1,6 @@
1
1
  export * from './actor-session.js';
2
2
  export * from './communication.js';
3
+ export * from './clinical-statuses.js';
3
4
  export * from './cryptography.js';
4
5
  export * from './data-capabilities.js';
5
6
  export * from './dataspace-discovery.js';
@@ -56,6 +56,7 @@ export function buildIpsClinicalHistoryBundleExample() {
56
56
  '@context': 'org.hl7.fhir.api',
57
57
  [MedicationStatementClaim.Identifier]: EXAMPLE_MEDICATION_STATEMENT_IDENTIFIER,
58
58
  [MedicationStatementClaim.Subject]: EXAMPLE_SUBJECT_DID,
59
+ [MedicationStatementClaim.Category]: HealthcareBasicSections.HistoryOfMedicationUse.attributeValue,
59
60
  [MedicationStatementClaim.Status]: EXAMPLE_MEDICATION_STATEMENT_STATUS,
60
61
  [MedicationStatementClaim.MedicationText]: EXAMPLE_MEDICATION_STATEMENT_TEXT,
61
62
  [MedicationStatementClaim.Effective]: '2026-05-05',
@@ -1,10 +1,10 @@
1
1
  // Copyright 2025 Antifraud Services Inc. under the Apache License, Version 2.0.
2
2
  // File: src/storage/IVaultRepository.ts
3
3
  // ---------------------------------------------------------------------------
4
- // Base contract — used by all channels (voice, chat, mobile)
4
+ // Base contract — used by all channels (chat, mobile, web, backend)
5
5
  //
6
6
  // Each vault instance is scoped to a single actor/session context
7
- // (e.g. callSid for voice, userId for text channels).
7
+ // (e.g. one transient session id or one user id).
8
8
  //
9
9
  // collectionName → logical bucket within the vault (e.g. 'tokens', 'session')
10
10
  // containerId → unique record id within the bucket
@@ -5,7 +5,7 @@ import { IVaultRepository, VaultQuery } from './IVaultRepository';
5
5
  *
6
6
  * Intended for:
7
7
  * - Unit/integration tests across all packages
8
- * - Short-lived runtime contexts (voice calls, transient sessions)
8
+ * - Short-lived runtime contexts (transient sessions)
9
9
  *
10
10
  * Each instance is independent — instantiate one per actor/session context
11
11
  * (e.g. one per CallSid in uhc-unid-chat-node) and discard on cleanup.
@@ -6,7 +6,7 @@ import { IVaultRepository } from './IVaultRepository.js';
6
6
  *
7
7
  * Intended for:
8
8
  * - Unit/integration tests across all packages
9
- * - Short-lived runtime contexts (voice calls, transient sessions)
9
+ * - Short-lived runtime contexts (transient sessions)
10
10
  *
11
11
  * Each instance is independent — instantiate one per actor/session context
12
12
  * (e.g. one per CallSid in uhc-unid-chat-node) and discard on cleanup.
@@ -67,7 +67,7 @@ export interface BuildDidcommPlaintextTransportMetadataInput {
67
67
  * Optional explicit content type copied into the mirrored technical JWS
68
68
  * header.
69
69
  *
70
- * Defaults to `application/didcomm-plaintext+json`.
70
+ * Defaults to `application/didcomm-plain+json`.
71
71
  */
72
72
  contentType?: string;
73
73
  }
@@ -103,7 +103,7 @@ export declare function buildOrganizationBindingInput(input: BuildOrganizationBi
103
103
  * Transport rule:
104
104
  * - in secure JOSE transport, these values belong in the protected JWS/JWE
105
105
  * headers of the real envelope
106
- * - in `application/didcomm-plaintext+json`, there is no signed outer
106
+ * - in `application/didcomm-plain+json`, there is no signed outer
107
107
  * envelope on the wire, so high-level SDK/BFF helpers may mirror the same
108
108
  * technical key identifiers and public JWKs into `meta.jws.protected` and
109
109
  * `meta.jwe.header`
@@ -122,7 +122,7 @@ export declare function buildDidcommPlaintextTransportMetadata(input: BuildDidco
122
122
  * Transport note:
123
123
  * - secure JOSE submission should place technical signing/encryption metadata
124
124
  * in the protected headers of the real JWS/JWE envelope
125
- * - demo `application/didcomm-plaintext+json` flows may mirror those same
125
+ * - demo `application/didcomm-plain+json` flows may mirror those same
126
126
  * values into plaintext `meta.jws.protected` / `meta.jwe.header` as a
127
127
  * technical fallback expected by GW-compatible backends
128
128
  * - that plaintext transport metadata must not replace the canonical
@@ -77,7 +77,7 @@ export function buildOrganizationBindingInput(input) {
77
77
  * Transport rule:
78
78
  * - in secure JOSE transport, these values belong in the protected JWS/JWE
79
79
  * headers of the real envelope
80
- * - in `application/didcomm-plaintext+json`, there is no signed outer
80
+ * - in `application/didcomm-plain+json`, there is no signed outer
81
81
  * envelope on the wire, so high-level SDK/BFF helpers may mirror the same
82
82
  * technical key identifiers and public JWKs into `meta.jws.protected` and
83
83
  * `meta.jwe.header`
@@ -98,7 +98,7 @@ export function buildDidcommPlaintextTransportMetadata(input) {
98
98
  protected: {
99
99
  alg: String(signingKey.alg || '').trim() || 'none',
100
100
  kid: String(signingKey.kid || '').trim() || 'none',
101
- cty: String(input.contentType || '').trim() || 'application/didcomm-plaintext+json',
101
+ cty: String(input.contentType || '').trim() || 'application/didcomm-plain+json',
102
102
  jwk: signingKey,
103
103
  },
104
104
  },
@@ -128,7 +128,7 @@ export function buildDidcommPlaintextTransportMetadata(input) {
128
128
  * Transport note:
129
129
  * - secure JOSE submission should place technical signing/encryption metadata
130
130
  * in the protected headers of the real JWS/JWE envelope
131
- * - demo `application/didcomm-plaintext+json` flows may mirror those same
131
+ * - demo `application/didcomm-plain+json` flows may mirror those same
132
132
  * values into plaintext `meta.jws.protected` / `meta.jwe.header` as a
133
133
  * technical fallback expected by GW-compatible backends
134
134
  * - that plaintext transport metadata must not replace the canonical
@@ -29,6 +29,28 @@ export type ClinicalResourceExpandedView = Readonly<{
29
29
  xhtml?: string;
30
30
  notes: string[];
31
31
  }>;
32
+ export type ClinicalResourceLike = Readonly<{
33
+ resourceType?: string;
34
+ text?: {
35
+ div?: unknown;
36
+ };
37
+ meta?: {
38
+ claims?: Record<string, unknown>;
39
+ };
40
+ note?: unknown;
41
+ code?: unknown;
42
+ valueQuantity?: unknown;
43
+ [key: string]: unknown;
44
+ }>;
45
+ export type LocalTextAndIntDisplay = Readonly<{
46
+ localText?: string;
47
+ internationalDisplay?: string;
48
+ combined?: string;
49
+ }>;
50
+ export type NarrativeResult = Readonly<{
51
+ xhtml?: string;
52
+ source: 'resource.text.div' | 'derived-from-claims' | 'missing';
53
+ }>;
32
54
  export type ClinicalResourceEntryLike = Readonly<{
33
55
  fullUrl?: string;
34
56
  type?: string;
@@ -65,3 +87,18 @@ export declare function toClinicalResourceExpandedView(entry: ClinicalResourceEn
65
87
  * Maps all entries from a Bundle into expanded views.
66
88
  */
67
89
  export declare function toClinicalResourceExpandedViews(bundle: ClinicalResourceBundleLike): ClinicalResourceExpandedView[];
90
+ /**
91
+ * Returns the most useful local text plus international display pair that can
92
+ * be inferred from one FHIR-like resource and its `meta.claims`.
93
+ */
94
+ export declare function getLocalTextAndIntDisplay(resource: ClinicalResourceLike): LocalTextAndIntDisplay;
95
+ /**
96
+ * Returns XHTML narrative for one FHIR-like resource, preferring
97
+ * `resource.text.div` and otherwise deriving a deterministic fallback from
98
+ * canonical `meta.claims`.
99
+ */
100
+ export declare function getXhtmlOrDerived(resource: ClinicalResourceLike): string | undefined;
101
+ /**
102
+ * Returns XHTML plus the source used to obtain it.
103
+ */
104
+ export declare function getNarrative(resource: ClinicalResourceLike): NarrativeResult;
@@ -4,7 +4,9 @@ import { ClaimConsent } from '../models/consent-rule.js';
4
4
  import { AllergyIntoleranceClaim, AllergyIntoleranceClaimsFhirApi, } from '../models/interoperable-claims/allergy-intolerance-claims.js';
5
5
  import { CommunicationClaim } from '../models/interoperable-claims/communication-claims.js';
6
6
  import { ConditionClaim, ConditionClaimsFhirApi, } from '../models/interoperable-claims/condition-claims.js';
7
- import { MedicationStatementClaim, MedicationStatementClaimsFhirApi, } from '../models/interoperable-claims/medication-statement-claims.js';
7
+ import { ImmunizationClaim } from '../models/interoperable-claims/immunization-claims.js';
8
+ import { MedicationStatementClaim, MedicationStatementClaimsFhirApi, MedicationStatementClaimsFhirApiExtended, } from '../models/interoperable-claims/medication-statement-claims.js';
9
+ import { ObservationClaim } from '../models/interoperable-claims/observation-claims.js';
8
10
  const CONSENT_ACTOR_REFERENCE_CLAIM = 'Consent.actor-reference';
9
11
  const GENERIC_CREATOR_CLAIM_SUFFIX = '.creator';
10
12
  const GENERIC_PERFORMER_CLAIM_SUFFIX = '.performer';
@@ -70,6 +72,73 @@ export function toClinicalResourceExpandedView(entry) {
70
72
  export function toClinicalResourceExpandedViews(bundle) {
71
73
  return readBundleEntries(bundle).map((entry) => toClinicalResourceExpandedView(entry));
72
74
  }
75
+ /**
76
+ * Returns the most useful local text plus international display pair that can
77
+ * be inferred from one FHIR-like resource and its `meta.claims`.
78
+ */
79
+ export function getLocalTextAndIntDisplay(resource) {
80
+ const claims = asRecord(resource?.meta?.claims);
81
+ const resourceType = resolveResourceType({ resource }, claims);
82
+ const localText = firstDefinedText([
83
+ resolveResourceCodeText(resource),
84
+ findBySuffix(claims, '.code-text'),
85
+ findBySuffix(claims, '.medication-text'),
86
+ findBySuffix(claims, '.vaccine-code-text'),
87
+ findBySuffix(claims, '.value-concept-text'),
88
+ ]) || resolveTitle(resourceType, claims);
89
+ const internationalDisplay = firstDefinedText([
90
+ resolveResourceCodeDisplay(resource),
91
+ findBySuffix(claims, '.code-display'),
92
+ findBySuffix(claims, '.vaccine-code-display'),
93
+ findBySuffix(claims, '.value-concept-display'),
94
+ ]);
95
+ const combined = buildCombinedLabel(localText, internationalDisplay);
96
+ return {
97
+ ...(localText ? { localText } : {}),
98
+ ...(internationalDisplay ? { internationalDisplay } : {}),
99
+ ...(combined ? { combined } : {}),
100
+ };
101
+ }
102
+ /**
103
+ * Returns XHTML narrative for one FHIR-like resource, preferring
104
+ * `resource.text.div` and otherwise deriving a deterministic fallback from
105
+ * canonical `meta.claims`.
106
+ */
107
+ export function getXhtmlOrDerived(resource) {
108
+ return getNarrative(resource).xhtml;
109
+ }
110
+ /**
111
+ * Returns XHTML plus the source used to obtain it.
112
+ */
113
+ export function getNarrative(resource) {
114
+ const fromFhirNarrative = trimValue(asRecord(resource?.text).div);
115
+ if (fromFhirNarrative) {
116
+ return {
117
+ xhtml: fromFhirNarrative,
118
+ source: 'resource.text.div',
119
+ };
120
+ }
121
+ const claims = asRecord(resource?.meta?.claims);
122
+ const fromSpecificClaim = findBySuffix(claims, '.xhtml')
123
+ || findBySuffix(claims, '.text-div');
124
+ if (fromSpecificClaim) {
125
+ return {
126
+ xhtml: fromSpecificClaim,
127
+ source: 'derived-from-claims',
128
+ };
129
+ }
130
+ const resourceType = resolveResourceType({ resource }, claims);
131
+ const lines = buildNarrativeLines(resourceType, claims, resource);
132
+ if (lines.length === 0) {
133
+ return {
134
+ source: 'missing',
135
+ };
136
+ }
137
+ return {
138
+ xhtml: `<div xmlns="http://www.w3.org/1999/xhtml">${lines.map((line) => `<p>${escapeHtml(line)}</p>`).join('')}</div>`,
139
+ source: 'derived-from-claims',
140
+ };
141
+ }
73
142
  function readClaims(entry) {
74
143
  const resourceClaims = asRecord(entry?.resource?.meta?.claims);
75
144
  const legacyClaims = asRecord(entry?.meta?.claims);
@@ -284,13 +353,16 @@ function resolveActors(resourceType, claims) {
284
353
  return out;
285
354
  }
286
355
  function resolveXhtml(entry, claims) {
287
- const fromFhirNarrative = trimValue(asRecord(entry?.resource?.text).div);
288
- if (fromFhirNarrative) {
289
- return fromFhirNarrative;
290
- }
291
- const fromSpecificClaim = findBySuffix(claims, '.xhtml')
292
- || findBySuffix(claims, '.text-div');
293
- return fromSpecificClaim || undefined;
356
+ if (!entry?.resource) {
357
+ return undefined;
358
+ }
359
+ const mergedResource = {
360
+ ...entry.resource,
361
+ meta: {
362
+ claims,
363
+ },
364
+ };
365
+ return getXhtmlOrDerived(mergedResource);
294
366
  }
295
367
  function resolveNotes(entry, resourceType, claims) {
296
368
  const notesFromResource = readFhirNoteArray(entry);
@@ -379,6 +451,211 @@ function findBySuffix(claims, keySuffix) {
379
451
  }
380
452
  return undefined;
381
453
  }
454
+ function resolveResourceCodeText(resource) {
455
+ return trimValue(asRecord(resource?.code).text) || undefined;
456
+ }
457
+ function resolveResourceCodeDisplay(resource) {
458
+ const coding = asArray(asRecord(resource?.code).coding);
459
+ for (const item of coding) {
460
+ const display = trimValue(asRecord(item).display);
461
+ if (display) {
462
+ return display;
463
+ }
464
+ }
465
+ return undefined;
466
+ }
467
+ function firstDefinedText(values) {
468
+ for (const value of values) {
469
+ const normalized = trimValue(value);
470
+ if (normalized) {
471
+ return normalized;
472
+ }
473
+ }
474
+ return undefined;
475
+ }
476
+ function buildCombinedLabel(localText, internationalDisplay) {
477
+ const local = trimValue(localText);
478
+ const intl = trimValue(internationalDisplay);
479
+ if (local && intl && local !== intl) {
480
+ return `${local} (${intl})`;
481
+ }
482
+ return local || intl || undefined;
483
+ }
484
+ function buildNarrativeLines(resourceType, claims, resource) {
485
+ const lines = [];
486
+ const label = getLocalTextAndIntDisplay(resource).combined || resolveTitle(resourceType, claims) || resourceType;
487
+ if (label) {
488
+ lines.push(label);
489
+ }
490
+ const date = resolveDate(resourceType, claims);
491
+ if (date) {
492
+ lines.push(`Date: ${date}`);
493
+ }
494
+ const periodStart = resolvePeriodStart(resourceType, claims);
495
+ const periodEnd = resolvePeriodEnd(resourceType, claims);
496
+ if (periodStart) {
497
+ lines.push(`Start: ${periodStart}`);
498
+ if (periodEnd) {
499
+ lines.push(`End: ${periodEnd}`);
500
+ }
501
+ }
502
+ appendFamilySpecificNarrativeLines(lines, resourceType, claims, resource);
503
+ return uniqueTokens(lines);
504
+ }
505
+ function appendFamilySpecificNarrativeLines(lines, resourceType, claims, resource) {
506
+ if (resourceType === ResourceTypesFhirR4.AllergyIntolerance) {
507
+ pushLine(lines, 'Clinical status', firstClaimValue(claims, [
508
+ AllergyIntoleranceClaim.ClinicalStatus,
509
+ AllergyIntoleranceClaimsFhirApi.ClinicalStatus,
510
+ ]));
511
+ pushLine(lines, 'Verification status', firstClaimValue(claims, [
512
+ AllergyIntoleranceClaim.VerificationStatus,
513
+ AllergyIntoleranceClaimsFhirApi.VerificationStatus,
514
+ ]));
515
+ pushLine(lines, 'Criticality', firstClaimValue(claims, [
516
+ AllergyIntoleranceClaim.Criticality,
517
+ AllergyIntoleranceClaimsFhirApi.Criticality,
518
+ ]));
519
+ pushLine(lines, 'Category', firstClaimCsvValue(claims, [
520
+ AllergyIntoleranceClaim.Category,
521
+ AllergyIntoleranceClaimsFhirApi.Category,
522
+ ]));
523
+ return;
524
+ }
525
+ if (resourceType === ResourceTypesFhirR4.Condition) {
526
+ pushLine(lines, 'Clinical status', firstClaimValue(claims, [
527
+ ConditionClaim.ClinicalStatus,
528
+ ConditionClaimsFhirApi.ClinicalStatus,
529
+ ]));
530
+ pushLine(lines, 'Verification status', firstClaimValue(claims, [
531
+ ConditionClaim.VerificationStatus,
532
+ ConditionClaimsFhirApi.VerificationStatus,
533
+ ]));
534
+ pushLine(lines, 'Severity', firstClaimValue(claims, [
535
+ ConditionClaim.Severity,
536
+ ConditionClaimsFhirApi.Severity,
537
+ ]));
538
+ pushLine(lines, 'Category', firstClaimCsvValue(claims, [
539
+ ConditionClaim.Category,
540
+ ConditionClaimsFhirApi.Category,
541
+ ]));
542
+ return;
543
+ }
544
+ if (resourceType === ResourceTypesFhirR4.MedicationStatement) {
545
+ pushLine(lines, 'Status', firstClaimValue(claims, [
546
+ MedicationStatementClaim.Status,
547
+ MedicationStatementClaimsFhirApi.Status,
548
+ ]));
549
+ pushLine(lines, 'Dose', buildQuantityLabel(firstDefinedText([
550
+ normalizeNumericValue(claims[MedicationStatementClaimsFhirApiExtended.DoseQuantityValue]),
551
+ normalizeNumericValue(claims['MedicationStatement.dose-quantity-value']),
552
+ ]), firstDefinedText([
553
+ trimValue(claims[MedicationStatementClaimsFhirApiExtended.DoseQuantityUnit]),
554
+ trimValue(claims['MedicationStatement.dose-quantity-unit']),
555
+ ])));
556
+ pushLine(lines, 'Timing', buildMedicationTimingLabel(claims));
557
+ pushLine(lines, 'Note', firstClaimValue(claims, [MedicationStatementClaim.Note]));
558
+ return;
559
+ }
560
+ if (resourceType === ResourceTypesFhirR4.Immunization) {
561
+ pushLine(lines, 'Status', trimValue(claims[ImmunizationClaim.Status]));
562
+ pushLine(lines, 'Vaccine', firstDefinedText([
563
+ findBySuffix(claims, '.vaccine-code-text'),
564
+ findBySuffix(claims, '.vaccine-code-display'),
565
+ trimValue(claims[ImmunizationClaim.VaccineCode]),
566
+ ]));
567
+ pushLine(lines, 'Performer', trimValue(claims[ImmunizationClaim.Performer]));
568
+ pushLine(lines, 'Note', trimValue(claims[ImmunizationClaim.Note]));
569
+ return;
570
+ }
571
+ if (resourceType === ResourceTypesFhirR4.Observation) {
572
+ appendObservationNarrativeLines(lines, claims, resource);
573
+ }
574
+ }
575
+ function appendObservationNarrativeLines(lines, claims, resource) {
576
+ const codeValue = firstDefinedText([
577
+ trimValue(claims[ObservationClaim.CodeValue]),
578
+ splitTokenCode(claims[ObservationClaim.Code]),
579
+ ]);
580
+ const systolic = normalizeNumericValue(claims[ObservationClaim.BloodPressureSystolicNumber]);
581
+ const diastolic = normalizeNumericValue(claims[ObservationClaim.BloodPressureDiastolicNumber]);
582
+ const unit = firstDefinedText([
583
+ trimValue(claims[ObservationClaim.ValueQuantityUnit]),
584
+ resolveObservationUnitFromResource(resource),
585
+ ]);
586
+ if (codeValue === '85354-9' || systolic || diastolic) {
587
+ if (systolic) {
588
+ pushLine(lines, 'Systolic', buildQuantityLabel(systolic, unit));
589
+ }
590
+ if (diastolic) {
591
+ pushLine(lines, 'Diastolic', buildQuantityLabel(diastolic, unit));
592
+ }
593
+ return;
594
+ }
595
+ const numericValue = normalizeNumericValue(claims[ObservationClaim.ValueQuantityNumber]);
596
+ if (numericValue || unit) {
597
+ pushLine(lines, 'Value', buildQuantityLabel(numericValue, unit));
598
+ }
599
+ pushLine(lines, 'Note', trimValue(claims[ObservationClaim.Note]));
600
+ }
601
+ function buildMedicationTimingLabel(claims) {
602
+ const frequency = firstDefinedText([
603
+ normalizeNumericValue(claims[MedicationStatementClaimsFhirApiExtended.TimingFrequency]),
604
+ normalizeNumericValue(claims['MedicationStatement.timing-frequency']),
605
+ ]);
606
+ const period = firstDefinedText([
607
+ normalizeNumericValue(claims[MedicationStatementClaimsFhirApiExtended.TimingPeriod]),
608
+ normalizeNumericValue(claims['MedicationStatement.timing-period']),
609
+ ]);
610
+ const unit = firstDefinedText([
611
+ trimValue(claims[MedicationStatementClaimsFhirApiExtended.TimingPeriodUnit]),
612
+ trimValue(claims['MedicationStatement.timing-period-unit']),
613
+ ]);
614
+ if (!frequency && !period && !unit) {
615
+ return undefined;
616
+ }
617
+ return [frequency ? `${frequency}x` : undefined, period ? `every ${period}` : undefined, unit].filter(Boolean).join(' ');
618
+ }
619
+ function buildQuantityLabel(value, unit) {
620
+ const normalizedValue = trimValue(value);
621
+ const normalizedUnit = trimValue(unit);
622
+ if (normalizedValue && normalizedUnit) {
623
+ return `${normalizedValue} ${normalizedUnit}`;
624
+ }
625
+ return normalizedValue || normalizedUnit || undefined;
626
+ }
627
+ function resolveObservationUnitFromResource(resource) {
628
+ const valueQuantity = asRecord(resource?.valueQuantity);
629
+ return trimValue(valueQuantity.unit) || trimValue(valueQuantity.code) || undefined;
630
+ }
631
+ function normalizeNumericValue(value) {
632
+ const normalized = trimValue(value);
633
+ return normalized || undefined;
634
+ }
635
+ function splitTokenCode(value) {
636
+ const normalized = trimValue(value);
637
+ if (!normalized) {
638
+ return undefined;
639
+ }
640
+ if (!normalized.includes('|')) {
641
+ return normalized;
642
+ }
643
+ const parts = normalized.split('|');
644
+ return trimValue(parts[parts.length - 1]) || undefined;
645
+ }
646
+ function pushLine(lines, label, value) {
647
+ const normalized = trimValue(value);
648
+ if (!normalized) {
649
+ return;
650
+ }
651
+ lines.push(`${label}: ${normalized}`);
652
+ }
653
+ function escapeHtml(value) {
654
+ return value
655
+ .replaceAll('&', '&amp;')
656
+ .replaceAll('<', '&lt;')
657
+ .replaceAll('>', '&gt;');
658
+ }
382
659
  function pushActor(out, actor) {
383
660
  const identifier = trimValue(actor.identifier);
384
661
  if (!identifier) {
@@ -414,3 +691,6 @@ function asRecord(value) {
414
691
  }
415
692
  return value;
416
693
  }
694
+ function asArray(value) {
695
+ return Array.isArray(value) ? value : [];
696
+ }
@@ -1,9 +1,9 @@
1
1
  import { CommunicationMode, type DidcommSubmitKind } from './didcomm-submit-policy';
2
- export declare const DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE: "application/didcomm-plaintext+json";
2
+ export declare const DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE: "application/didcomm-plain+json";
3
3
  export declare const DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE: "application/didcomm-encrypted+json";
4
- export declare const DIDCOMM_DEFAULT_ACCEPT_HEADER: "application/json, application/didcomm-plaintext+json, */*";
4
+ export declare const DIDCOMM_DEFAULT_ACCEPT_HEADER: "application/json, application/didcomm-plain+json, */*";
5
5
  export declare const DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND: Readonly<{
6
- readonly plain: "application/didcomm-plaintext+json";
6
+ readonly plain: "application/didcomm-plain+json";
7
7
  readonly encrypted: "application/didcomm-encrypted+json";
8
8
  }>;
9
9
  export type DidcommFetchInit = {
@@ -1,5 +1,5 @@
1
1
  import { DIDCOMM_SUBMIT_KINDS, resolveDidcommSubmissionPlan, } from './didcomm-submit-policy.js';
2
- export const DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE = 'application/didcomm-plaintext+json';
2
+ export const DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE = 'application/didcomm-plain+json';
3
3
  export const DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE = 'application/didcomm-encrypted+json';
4
4
  export const DIDCOMM_DEFAULT_ACCEPT_HEADER = `application/json, ${DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE}, */*`;
5
5
  export const DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND = Object.freeze({
@@ -90,7 +90,7 @@ export declare const LEGAL_ORGANIZATION_ONBOARDING_JSON_SCHEMA: {
90
90
  * canonical `identifier.value`
91
91
  *
92
92
  * The result shape is intentionally assistant-friendly:
93
- * - `missingClaims` tells UI/voice flows what is still required
93
+ * - `missingClaims` tells UI/application flows what is still required
94
94
  * - `normalizedClaims` shows the post-derivation claim set
95
95
  * - `derived` explains which values were filled automatically
96
96
  */
@@ -67,7 +67,7 @@ function normalizeOptionalString(value) {
67
67
  * canonical `identifier.value`
68
68
  *
69
69
  * The result shape is intentionally assistant-friendly:
70
- * - `missingClaims` tells UI/voice flows what is still required
70
+ * - `missingClaims` tells UI/application flows what is still required
71
71
  * - `normalizedClaims` shows the post-derivation claim set
72
72
  * - `derived` explains which values were filled automatically
73
73
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },