gdc-common-utils-ts 2.0.6 → 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 +8 -0
- package/dist/constants/clinical-statuses.d.ts +83 -0
- package/dist/constants/clinical-statuses.js +74 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/examples/ips-bundle.js +1 -0
- package/dist/storage/IVaultRepository.js +2 -2
- package/dist/storage/VaultMemRepository.d.ts +1 -1
- package/dist/storage/VaultMemRepository.js +1 -1
- package/dist/utils/clinical-resource-view.d.ts +37 -0
- package/dist/utils/clinical-resource-view.js +288 -8
- package/dist/utils/legal-organization-onboarding.d.ts +1 -1
- package/dist/utils/legal-organization-onboarding.js +1 -1
- package/package.json +1 -1
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
|
+
});
|
package/dist/constants/index.js
CHANGED
|
@@ -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 (
|
|
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.
|
|
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 (
|
|
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 (
|
|
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.
|
|
@@ -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 {
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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('&', '&')
|
|
656
|
+
.replaceAll('<', '<')
|
|
657
|
+
.replaceAll('>', '>');
|
|
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
|
+
}
|
|
@@ -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/
|
|
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/
|
|
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
|
*/
|