@imranq2/fhirpatientsummary 1.0.37 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +410 -103
- package/dist/index.js +410 -103
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -110,6 +110,85 @@ var ESSENTIAL_LAB_PANELS = {
|
|
|
110
110
|
"80235-8": "Cardiac Markers Panel",
|
|
111
111
|
"69738-3": "CBC with Auto Differential"
|
|
112
112
|
};
|
|
113
|
+
var FUNCTIONAL_STATUS_SNOMED_CODES = {
|
|
114
|
+
"118233009": "Core Functional Status",
|
|
115
|
+
"364644000": "Core Functional Status",
|
|
116
|
+
"160245001": "Core Functional Status",
|
|
117
|
+
"21134002": "Disability & Impairment",
|
|
118
|
+
"2655002": "Disability & Impairment",
|
|
119
|
+
"2219003": "Disability & Impairment",
|
|
120
|
+
"962000": "Disability & Impairment",
|
|
121
|
+
"3089009": "Disability & Impairment",
|
|
122
|
+
"4197006": "Disability & Impairment",
|
|
123
|
+
"284545001": "Activities of Daily Living",
|
|
124
|
+
"719930002": "Activities of Daily Living",
|
|
125
|
+
"301438001": "Activities of Daily Living",
|
|
126
|
+
"282145008": "Mobility & Movement",
|
|
127
|
+
"165245003": "Mobility & Movement",
|
|
128
|
+
"713458007": "Mobility & Movement"
|
|
129
|
+
};
|
|
130
|
+
var FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES = {
|
|
131
|
+
"54522-8": "Functional Status Panel",
|
|
132
|
+
"54523-6": "ADL Assistance",
|
|
133
|
+
"45588-1": "Bed Mobility",
|
|
134
|
+
"45589-9": "Bed Mobility",
|
|
135
|
+
"45590-7": "Transfer",
|
|
136
|
+
"45591-5": "Transfer",
|
|
137
|
+
"45592-3": "Walk in room",
|
|
138
|
+
"45593-1": "Walk in room",
|
|
139
|
+
"45594-9": "Walk in corridor",
|
|
140
|
+
"45595-6": "Walk in corridor",
|
|
141
|
+
"45596-4": "Locomotion on unit",
|
|
142
|
+
"45597-2": "Locomotion on unit",
|
|
143
|
+
"45598-0": "Locomotion off unit",
|
|
144
|
+
"45599-8": "Locomotion off unit",
|
|
145
|
+
"45600-4": "Dressing",
|
|
146
|
+
"45601-2": "Dressing",
|
|
147
|
+
"45602-0": "Eating",
|
|
148
|
+
"45603-8": "Eating",
|
|
149
|
+
"45604-6": "Toilet use",
|
|
150
|
+
"45605-3": "Toilet use",
|
|
151
|
+
"45606-1": "Personal hygiene",
|
|
152
|
+
"45607-9": "Personal hygiene",
|
|
153
|
+
"46008-9": "Bathing",
|
|
154
|
+
"45608-7": "Bathing",
|
|
155
|
+
"45609-5": "Bathing",
|
|
156
|
+
"54524-4": "Balance during Transitions and Walking",
|
|
157
|
+
"54749-7": "Moving from Seated to Standing",
|
|
158
|
+
"54750-5": "Walking with Assistive Device",
|
|
159
|
+
"54751-3": "Turning Around While Walking",
|
|
160
|
+
"54753-9": "Surface-to-Surface Transfer",
|
|
161
|
+
"54525-1": "Functional Limitation in Range of Motion",
|
|
162
|
+
"54754-7": "Upper Extremity ROM",
|
|
163
|
+
"54755-4": "Lower Extremity ROM",
|
|
164
|
+
"54526-9": "Mobility Devices",
|
|
165
|
+
"54756-2": "Cane/Crutch",
|
|
166
|
+
"54757-0": "Walker",
|
|
167
|
+
"54758-8": "Wheelchair",
|
|
168
|
+
"54759-6": "Limb Prosthesis",
|
|
169
|
+
"54760-4": "No Mobility Devices",
|
|
170
|
+
"54527-7": "Functional Rehabilitation Potential",
|
|
171
|
+
"55123-4": "Resident Believes Capable of Increased Independence",
|
|
172
|
+
"45613-7": "Staff Believes Capable of Increased Independence"
|
|
173
|
+
};
|
|
174
|
+
var ADVANCED_DIRECTIVE_CATEGORY_CODES = {
|
|
175
|
+
acd: "Advance Directive",
|
|
176
|
+
dnr: "Do Not Resuscitate",
|
|
177
|
+
polst: "POLST",
|
|
178
|
+
hcd: "Health Care Directive"
|
|
179
|
+
};
|
|
180
|
+
var ADVANCED_DIRECTIVE_CATEGORY_SYSTEM = "http://terminology.hl7.org/CodeSystem/consentcategorycodes";
|
|
181
|
+
var ADVANCED_DIRECTIVE_LOINC_CODES = {
|
|
182
|
+
"45473-6": "Advance healthcare directive Completed",
|
|
183
|
+
"45474-4": "Do not resuscitate",
|
|
184
|
+
"45475-1": "Do not hospitalize",
|
|
185
|
+
"45476-9": "Organ donation",
|
|
186
|
+
"45477-7": "Autopsy request",
|
|
187
|
+
"45478-5": "Feeding restrictions",
|
|
188
|
+
"45479-3": "Medication restrictions",
|
|
189
|
+
"45480-1": "Other treatment restrictions"
|
|
190
|
+
};
|
|
191
|
+
var LOINC_SYSTEM = "http://loinc.org";
|
|
113
192
|
|
|
114
193
|
// src/structures/ips_section_constants.ts
|
|
115
194
|
var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
@@ -145,19 +224,16 @@ var IPSSectionResourceFilters = {
|
|
|
145
224
|
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
|
|
146
225
|
// Only include pregnancy history Observations or relevant Conditions
|
|
147
226
|
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "Condition" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")),
|
|
148
|
-
// Only include
|
|
149
|
-
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "
|
|
150
|
-
(cat) => cat.coding?.some(
|
|
151
|
-
(c) => c.code === "functional-status" && c.system === "http://terminology.hl7.org/CodeSystem/observation-category" || typeof c.display === "string" && c.display.toLowerCase().includes("functional")
|
|
152
|
-
)
|
|
227
|
+
// Only include Condition with Functional Status LOINC and SNOMED codes, category code 'problem-list-item', and completed ClinicalImpressions
|
|
228
|
+
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && ((codeableConceptMatches(resource.code, Object.keys(FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(FUNCTIONAL_STATUS_SNOMED_CODES), "http://snomed.info/sct")) && resource.clinicalStatus?.coding?.some((c) => c.code === "active") && resource.category?.some(
|
|
229
|
+
(cat) => cat.coding?.some((c) => c.code === "problem-list-item")
|
|
153
230
|
)) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
|
|
154
231
|
// Only include resolved medical history Conditions
|
|
155
232
|
["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
|
|
156
233
|
// Only include active care plans
|
|
157
234
|
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
|
|
158
235
|
// Only include active advance directives (Consent resources)
|
|
159
|
-
|
|
160
|
-
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
|
|
236
|
+
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active" && resource.scope?.coding?.some((c) => codingMatches(c, "adr", "http://terminology.hl7.org/CodeSystem/consentscope"))
|
|
161
237
|
};
|
|
162
238
|
var IPSSectionSummaryCompositionFilter = {
|
|
163
239
|
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
@@ -173,8 +249,10 @@ var IPSSectionSummaryCompositionFilter = {
|
|
|
173
249
|
var IPSSectionSummaryIPSCompositionFilter = {
|
|
174
250
|
["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_patient_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
175
251
|
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
252
|
+
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_advanced_directives_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
176
253
|
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_social_history_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
177
|
-
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, ["ips_functional_status_condition_summary_document", "ips_functional_status_clinical_impression_summary_document"], IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
|
|
254
|
+
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, ["ips_functional_status_condition_summary_document", "ips_functional_status_clinical_impression_summary_document"], IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
255
|
+
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_medical_device_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
|
|
178
256
|
};
|
|
179
257
|
var IPSSectionResourceHelper = class {
|
|
180
258
|
static getResourceFilterForSection(section) {
|
|
@@ -306,13 +384,26 @@ var TemplateUtilities = class {
|
|
|
306
384
|
return code ? `${code} (${systemDisplay})` : "";
|
|
307
385
|
}
|
|
308
386
|
/**
|
|
309
|
-
*
|
|
387
|
+
* Resolves and returns a Device resource from a Device Reference.
|
|
388
|
+
* Returns null if the reference cannot be resolved or is not a Device.
|
|
310
389
|
* @param deviceRef - Reference to a Device resource
|
|
311
|
-
* @returns
|
|
390
|
+
* @returns The resolved Device resource or null
|
|
391
|
+
*/
|
|
392
|
+
getDeviceFromReference(deviceRef) {
|
|
393
|
+
if (!deviceRef) return null;
|
|
394
|
+
const device = this.resolveReference(deviceRef);
|
|
395
|
+
return device && device.resourceType === "Device" ? device : null;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Renders a Device name from a Device reference.
|
|
399
|
+
* Uses getDeviceFromReference to resolve the Device and returns a concatenated device.deviceName.
|
|
400
|
+
* Returns an empty string if no deviceName is available. The returned text is HTML-safe.
|
|
401
|
+
* @param deviceRef - Reference to a Device resource
|
|
402
|
+
* @returns Sanitized device name or empty string
|
|
312
403
|
*/
|
|
313
404
|
renderDevice(deviceRef) {
|
|
314
|
-
const device =
|
|
315
|
-
if (device && device.
|
|
405
|
+
const device = this.getDeviceFromReference(deviceRef);
|
|
406
|
+
if (device && device.deviceName && device.deviceName.length > 0) {
|
|
316
407
|
return this.safeConcat(device.deviceName, "name");
|
|
317
408
|
}
|
|
318
409
|
return "";
|
|
@@ -2728,11 +2819,64 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2728
2819
|
return _MedicalDevicesTemplate.generateStaticNarrative(resources, timezone);
|
|
2729
2820
|
}
|
|
2730
2821
|
/**
|
|
2731
|
-
*
|
|
2732
|
-
* @param resources - FHIR
|
|
2822
|
+
* Generate HTML narrative for history of medical devices using summary
|
|
2823
|
+
* @param resources - FHIR Composition resources
|
|
2733
2824
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2734
2825
|
* @returns HTML string for rendering
|
|
2735
2826
|
*/
|
|
2827
|
+
generateSummaryNarrative(resources, timezone) {
|
|
2828
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2829
|
+
let isSummaryCreated = false;
|
|
2830
|
+
let html = `<p>This list includes all information about the patient's medical devices history, sorted by recorded date (most recent first).</p>
|
|
2831
|
+
`;
|
|
2832
|
+
html += `
|
|
2833
|
+
<table>
|
|
2834
|
+
<thead>
|
|
2835
|
+
<tr>
|
|
2836
|
+
<th>Device</th>
|
|
2837
|
+
<th>Code (System)</th>
|
|
2838
|
+
<th>Status</th>
|
|
2839
|
+
<th>Comments</th>
|
|
2840
|
+
<th>Date Recorded</th>
|
|
2841
|
+
<th>Source</th>
|
|
2842
|
+
</tr>
|
|
2843
|
+
</thead>
|
|
2844
|
+
<tbody>`;
|
|
2845
|
+
for (const resourceItem of resources) {
|
|
2846
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
2847
|
+
const sectionCodeableConcept = rowData.code;
|
|
2848
|
+
const data = {};
|
|
2849
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
2850
|
+
for (const columnData of rowData.section ?? []) {
|
|
2851
|
+
const columnTitle = columnData.title;
|
|
2852
|
+
if (columnTitle) {
|
|
2853
|
+
data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
if (data["Device Name"]?.toLowerCase() === "unknown") {
|
|
2857
|
+
continue;
|
|
2858
|
+
}
|
|
2859
|
+
isSummaryCreated = true;
|
|
2860
|
+
html += `
|
|
2861
|
+
<tr>
|
|
2862
|
+
<td>${templateUtilities.capitalizeFirstLetter(data["Device Name"] ?? "")}</td>
|
|
2863
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
2864
|
+
<td>${templateUtilities.renderTextAsHtml(data["Status"] ?? "")}</td>
|
|
2865
|
+
<td>${templateUtilities.renderTextAsHtml(data["Notes"] ?? "")}</td>
|
|
2866
|
+
<td>${templateUtilities.renderTime(data["Recorded On"] ?? "", timezone)}</td>
|
|
2867
|
+
<td>${data["Source"] ?? ""}</td>
|
|
2868
|
+
</tr>`;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
html += `
|
|
2872
|
+
</tbody>
|
|
2873
|
+
</table>`;
|
|
2874
|
+
return isSummaryCreated ? html : void 0;
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Internal static implementation that generates the narrative from DeviceUseStatement resources.
|
|
2878
|
+
* The “Code (System)” column renders Device.type as "code (SystemDisplay)" using TemplateUtilities.codeableConceptCoding.
|
|
2879
|
+
*/
|
|
2736
2880
|
static generateStaticNarrative(resources, timezone) {
|
|
2737
2881
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2738
2882
|
let html = `<p>This list includes all DeviceUseStatement resources, sorted by recorded date (most recent first).</p>
|
|
@@ -2740,6 +2884,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2740
2884
|
<thead>
|
|
2741
2885
|
<tr>
|
|
2742
2886
|
<th>Device</th>
|
|
2887
|
+
<th>Code (System)</th>
|
|
2743
2888
|
<th>Status</th>
|
|
2744
2889
|
<th>Comments</th>
|
|
2745
2890
|
<th>Date Recorded</th>
|
|
@@ -2752,26 +2897,32 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2752
2897
|
const dateB = b.recordedOn;
|
|
2753
2898
|
return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
2754
2899
|
});
|
|
2755
|
-
|
|
2900
|
+
const devicesAdded = /* @__PURE__ */ new Set();
|
|
2756
2901
|
for (const dus of deviceStatements) {
|
|
2757
|
-
const deviceName = templateUtilities.
|
|
2758
|
-
if (deviceName?.toLowerCase() === "unknown") {
|
|
2902
|
+
const deviceName = templateUtilities.renderDevice(dus.device);
|
|
2903
|
+
if (deviceName?.toLowerCase() === "unknown" || devicesAdded.has(deviceName)) {
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2906
|
+
const device = templateUtilities.getDeviceFromReference(dus.device);
|
|
2907
|
+
const codeSystem = templateUtilities.codeableConceptCoding(device?.type);
|
|
2908
|
+
if (!codeSystem) {
|
|
2759
2909
|
continue;
|
|
2760
2910
|
}
|
|
2761
|
-
|
|
2911
|
+
devicesAdded.add(deviceName);
|
|
2762
2912
|
html += `
|
|
2763
2913
|
<tr>
|
|
2764
2914
|
<td>${templateUtilities.capitalizeFirstLetter(deviceName)}</td>
|
|
2915
|
+
<td>${codeSystem}</td>
|
|
2765
2916
|
<td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
|
|
2766
2917
|
<td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
|
|
2767
|
-
<td>${templateUtilities.
|
|
2918
|
+
<td>${templateUtilities.renderTime(dus.recordedOn, timezone)}</td>
|
|
2768
2919
|
<td>${templateUtilities.getOwnerTag(dus)}</td>
|
|
2769
2920
|
</tr>`;
|
|
2770
2921
|
}
|
|
2771
2922
|
html += `
|
|
2772
2923
|
</tbody>
|
|
2773
2924
|
</table>`;
|
|
2774
|
-
return
|
|
2925
|
+
return devicesAdded.size > 0 ? html : void 0;
|
|
2775
2926
|
}
|
|
2776
2927
|
};
|
|
2777
2928
|
|
|
@@ -4075,7 +4226,7 @@ var PlanOfCareTemplate = class {
|
|
|
4075
4226
|
var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
4076
4227
|
/**
|
|
4077
4228
|
* Generate HTML narrative for Functional Status
|
|
4078
|
-
* @param resources - FHIR resources array containing
|
|
4229
|
+
* @param resources - FHIR resources array containing Condition and ClinicalImpression resources
|
|
4079
4230
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
4080
4231
|
* @returns HTML string for rendering
|
|
4081
4232
|
*/
|
|
@@ -4115,8 +4266,8 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
4115
4266
|
<thead>
|
|
4116
4267
|
<tr>
|
|
4117
4268
|
<th>Name</th>
|
|
4118
|
-
<th>Date</th>
|
|
4119
4269
|
<th>Code (System)</th>
|
|
4270
|
+
<th>Date</th>
|
|
4120
4271
|
<th>Description</th>
|
|
4121
4272
|
<th>Summary</th>
|
|
4122
4273
|
<th>Source</th>
|
|
@@ -4182,8 +4333,8 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
4182
4333
|
clinicalImpressionsHtml += `
|
|
4183
4334
|
<tr>
|
|
4184
4335
|
<td>${templateUtilities.capitalizeFirstLetter(name)}</td>
|
|
4185
|
-
<td>${date ?? ""}</td>
|
|
4186
4336
|
<td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
|
|
4337
|
+
<td>${date ?? ""}</td>
|
|
4187
4338
|
<td>${data["Description"] ?? ""}</td>
|
|
4188
4339
|
<td>${data["Summary"] ?? ""}</td>
|
|
4189
4340
|
<td>${data["Source"] ?? ""}</td>
|
|
@@ -4212,92 +4363,156 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
4212
4363
|
}
|
|
4213
4364
|
/**
|
|
4214
4365
|
* Internal static implementation that actually generates the narrative
|
|
4215
|
-
* @param resources - FHIR resources array containing
|
|
4366
|
+
* @param resources - FHIR resources array containing Condition and ClinicalImpression resources
|
|
4216
4367
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
4217
4368
|
* @returns HTML string for rendering
|
|
4218
4369
|
*/
|
|
4219
4370
|
static generateStaticNarrative(resources, timezone) {
|
|
4220
4371
|
const templateUtilities = new TemplateUtilities(resources);
|
|
4221
|
-
let html = `<
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
let
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4372
|
+
let html = `<div>
|
|
4373
|
+
<p>This section summarizes key conditions and assessments related to the person's functional status and ability to perform daily activities.</p>`;
|
|
4374
|
+
let conditionHtml = `
|
|
4375
|
+
<div>
|
|
4376
|
+
<h3>Conditions</h3>
|
|
4377
|
+
<table>
|
|
4378
|
+
<thead>
|
|
4379
|
+
<tr>
|
|
4380
|
+
<th>Problem</th>
|
|
4381
|
+
<th>Code (System)</th>
|
|
4382
|
+
<th>Onset Date</th>
|
|
4383
|
+
<th>Recorded Date</th>
|
|
4384
|
+
<th>Source</th>
|
|
4385
|
+
</tr>
|
|
4386
|
+
</thead>
|
|
4387
|
+
<tbody>`;
|
|
4388
|
+
let clinicalImpressionsHtml = `
|
|
4389
|
+
<div>
|
|
4390
|
+
<h3>Clinical Impressions</h3>
|
|
4391
|
+
<table>
|
|
4392
|
+
<thead>
|
|
4393
|
+
<tr>
|
|
4394
|
+
<th>Name</th>
|
|
4395
|
+
<th>Code (System)</th>
|
|
4396
|
+
<th>Date</th>
|
|
4397
|
+
<th>Description</th>
|
|
4398
|
+
<th>Summary</th>
|
|
4399
|
+
<th>Source</th>
|
|
4400
|
+
</tr>
|
|
4401
|
+
</thead>
|
|
4402
|
+
<tbody>`;
|
|
4403
|
+
const conditions = resources.filter((entry) => entry.resourceType === "Condition").map((entry) => entry);
|
|
4404
|
+
conditions.sort((a, b) => {
|
|
4405
|
+
const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
|
|
4406
|
+
const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
|
|
4407
|
+
return dateB - dateA;
|
|
4241
4408
|
});
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4409
|
+
const addedConditions = /* @__PURE__ */ new Set();
|
|
4410
|
+
for (const cond of conditions) {
|
|
4411
|
+
const functionalStatusName = this.getFunctionalStatusNameFromCode(cond.code);
|
|
4412
|
+
const problem = templateUtilities.codeableConceptDisplay(cond.code) || functionalStatusName;
|
|
4413
|
+
const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
|
|
4414
|
+
if (!codeAndSystem || !problem || !functionalStatusName || addedConditions.has(functionalStatusName)) {
|
|
4415
|
+
continue;
|
|
4416
|
+
}
|
|
4417
|
+
if (problem?.toLowerCase() === "unknown") {
|
|
4418
|
+
continue;
|
|
4419
|
+
}
|
|
4420
|
+
addedConditions.add(functionalStatusName);
|
|
4421
|
+
let date = cond.onsetDateTime ? templateUtilities.renderTime(cond.onsetDateTime, timezone) : "";
|
|
4422
|
+
if (!date && cond.onsetPeriod?.start) {
|
|
4423
|
+
date = templateUtilities.renderTime(
|
|
4424
|
+
cond.onsetPeriod?.start,
|
|
4425
|
+
timezone
|
|
4426
|
+
);
|
|
4427
|
+
if (cond.onsetPeriod?.end) {
|
|
4428
|
+
date += " - " + templateUtilities.renderTime(cond.onsetPeriod?.end, timezone);
|
|
4249
4429
|
}
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
html += `<tr>
|
|
4255
|
-
<td>${templateUtilities.capitalizeFirstLetter(obsName)}</td>
|
|
4256
|
-
<td>${value ?? ""}</td>
|
|
4430
|
+
}
|
|
4431
|
+
conditionHtml += `<tr>
|
|
4432
|
+
<td>${templateUtilities.capitalizeFirstLetter(problem)}</td>
|
|
4433
|
+
<td>${codeAndSystem}</td>
|
|
4257
4434
|
<td>${date}</td>
|
|
4258
|
-
<td>${
|
|
4259
|
-
<td>${
|
|
4260
|
-
<td>${templateUtilities.getOwnerTag(observation)}</td>
|
|
4435
|
+
<td>${templateUtilities.renderTime(cond.recordedDate, timezone)}</td>
|
|
4436
|
+
<td>${templateUtilities.getOwnerTag(cond)}</td>
|
|
4261
4437
|
</tr>`;
|
|
4438
|
+
}
|
|
4439
|
+
const clinicalImpressions = resources.filter((entry) => entry.resourceType === "ClinicalImpression").map((entry) => entry);
|
|
4440
|
+
clinicalImpressions.sort((a, b) => {
|
|
4441
|
+
const dateA = this.getClinicalImpressionEffectiveDate(a);
|
|
4442
|
+
const dateB = this.getClinicalImpressionEffectiveDate(b);
|
|
4443
|
+
return dateB && dateA ? dateB.getTime() - dateA.getTime() : 0;
|
|
4444
|
+
});
|
|
4445
|
+
const addedClinicalImpressions = /* @__PURE__ */ new Set();
|
|
4446
|
+
for (const impression of clinicalImpressions) {
|
|
4447
|
+
const name = templateUtilities.codeableConceptDisplay(impression.code);
|
|
4448
|
+
const codeAndSystem = templateUtilities.codeableConceptCoding(impression.code);
|
|
4449
|
+
if (!codeAndSystem || addedClinicalImpressions.has(name)) {
|
|
4450
|
+
continue;
|
|
4262
4451
|
}
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
}
|
|
4276
|
-
let findingsHtml = "";
|
|
4277
|
-
if (impression.finding && impression.finding.length > 0) {
|
|
4278
|
-
findingsHtml = "<ul>";
|
|
4279
|
-
for (const finding of impression.finding) {
|
|
4280
|
-
const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConceptDisplay(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
|
|
4281
|
-
const cause = finding.basis || "";
|
|
4282
|
-
findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
|
|
4283
|
-
}
|
|
4284
|
-
findingsHtml += "</ul>";
|
|
4452
|
+
if (!name || name?.toLowerCase() === "unknown") {
|
|
4453
|
+
continue;
|
|
4454
|
+
}
|
|
4455
|
+
addedClinicalImpressions.add(name);
|
|
4456
|
+
let date = impression.effectiveDateTime ? templateUtilities.renderTime(impression.effectiveDateTime, timezone) : "";
|
|
4457
|
+
if (!date && impression.effectivePeriod?.start) {
|
|
4458
|
+
date = templateUtilities.renderTime(
|
|
4459
|
+
impression.effectivePeriod?.start,
|
|
4460
|
+
timezone
|
|
4461
|
+
);
|
|
4462
|
+
if (impression.effectivePeriod?.end) {
|
|
4463
|
+
date += " - " + templateUtilities.renderTime(impression.effectivePeriod?.end, timezone);
|
|
4285
4464
|
}
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
<td>${
|
|
4465
|
+
}
|
|
4466
|
+
clinicalImpressionsHtml += `<tr>
|
|
4467
|
+
<td>${templateUtilities.capitalizeFirstLetter(name)}</td>
|
|
4468
|
+
<td>${codeAndSystem}</td>
|
|
4469
|
+
<td>${date}</td>
|
|
4289
4470
|
<td>${impression.description || ""}</td>
|
|
4290
4471
|
<td>${impression.summary || ""}</td>
|
|
4291
|
-
<td>${findingsHtml}</td>
|
|
4292
4472
|
<td>${templateUtilities.getOwnerTag(impression)}</td>
|
|
4293
4473
|
</tr>`;
|
|
4474
|
+
}
|
|
4475
|
+
if (addedConditions.size > 0) {
|
|
4476
|
+
html += conditionHtml;
|
|
4477
|
+
html += `
|
|
4478
|
+
</tbody>
|
|
4479
|
+
</table>
|
|
4480
|
+
</div>`;
|
|
4481
|
+
}
|
|
4482
|
+
if (addedClinicalImpressions.size > 0) {
|
|
4483
|
+
html += clinicalImpressionsHtml;
|
|
4484
|
+
html += `
|
|
4485
|
+
</tbody>
|
|
4486
|
+
</table>
|
|
4487
|
+
</div>`;
|
|
4488
|
+
}
|
|
4489
|
+
html += `
|
|
4490
|
+
</div>`;
|
|
4491
|
+
return addedConditions.size > 0 || addedClinicalImpressions.size > 0 ? html : void 0;
|
|
4492
|
+
}
|
|
4493
|
+
static getFunctionalStatusNameFromCode(cc) {
|
|
4494
|
+
if (!cc) return "";
|
|
4495
|
+
for (const coding of cc.coding || []) {
|
|
4496
|
+
let functionalStatusName = FUNCTIONAL_STATUS_SNOMED_CODES[coding.code];
|
|
4497
|
+
if (functionalStatusName) {
|
|
4498
|
+
return functionalStatusName;
|
|
4499
|
+
}
|
|
4500
|
+
functionalStatusName = FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES[coding.code];
|
|
4501
|
+
if (functionalStatusName) {
|
|
4502
|
+
return functionalStatusName;
|
|
4294
4503
|
}
|
|
4295
|
-
html += `</tbody></table>`;
|
|
4296
4504
|
}
|
|
4297
|
-
|
|
4298
|
-
|
|
4505
|
+
}
|
|
4506
|
+
static getClinicalImpressionEffectiveDate(impression) {
|
|
4507
|
+
if (impression.effectiveDateTime) {
|
|
4508
|
+
return new Date(impression.effectiveDateTime);
|
|
4509
|
+
} else if (impression.effectivePeriod) {
|
|
4510
|
+
if (impression.effectivePeriod.start) {
|
|
4511
|
+
return new Date(impression.effectivePeriod.start);
|
|
4512
|
+
} else if (impression.effectivePeriod.end) {
|
|
4513
|
+
return new Date(impression.effectivePeriod.end);
|
|
4514
|
+
}
|
|
4299
4515
|
}
|
|
4300
|
-
return html;
|
|
4301
4516
|
}
|
|
4302
4517
|
};
|
|
4303
4518
|
|
|
@@ -4448,7 +4663,78 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
4448
4663
|
const dateB = new Date(b.dateTime || 0);
|
|
4449
4664
|
return dateB.getTime() - dateA.getTime();
|
|
4450
4665
|
});
|
|
4451
|
-
return _AdvanceDirectivesTemplate.generateStaticNarrative(
|
|
4666
|
+
return _AdvanceDirectivesTemplate.generateStaticNarrative(
|
|
4667
|
+
resources,
|
|
4668
|
+
timezone
|
|
4669
|
+
);
|
|
4670
|
+
}
|
|
4671
|
+
/**
|
|
4672
|
+
* Generate HTML narrative for advance directives using summary
|
|
4673
|
+
* @param resources - FHIR Composition resources
|
|
4674
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
4675
|
+
* @returns HTML string for rendering
|
|
4676
|
+
*/
|
|
4677
|
+
generateSummaryNarrative(resources, timezone) {
|
|
4678
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
4679
|
+
let isSummaryCreated = false;
|
|
4680
|
+
let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
|
|
4681
|
+
`;
|
|
4682
|
+
html += `
|
|
4683
|
+
<div>
|
|
4684
|
+
<table>
|
|
4685
|
+
<thead>
|
|
4686
|
+
<tr>
|
|
4687
|
+
<th>Directive</th>
|
|
4688
|
+
<th>Code (System)</th>
|
|
4689
|
+
<th>Action Controlled</th>
|
|
4690
|
+
<th>Policy Rule</th>
|
|
4691
|
+
<th>Date</th>
|
|
4692
|
+
<th>Source</th>
|
|
4693
|
+
</tr>
|
|
4694
|
+
</thead>
|
|
4695
|
+
<tbody>`;
|
|
4696
|
+
for (const resourceItem of resources) {
|
|
4697
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
4698
|
+
const sectionCodeableConcept = rowData.code;
|
|
4699
|
+
const data = {};
|
|
4700
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(
|
|
4701
|
+
sectionCodeableConcept
|
|
4702
|
+
);
|
|
4703
|
+
for (const columnData of rowData.section ?? []) {
|
|
4704
|
+
const columnTitle = columnData.title;
|
|
4705
|
+
if (columnTitle) {
|
|
4706
|
+
if (columnTitle === "DateTime") {
|
|
4707
|
+
data[columnTitle] = templateUtilities.renderTime(
|
|
4708
|
+
columnData.text?.div ?? "",
|
|
4709
|
+
timezone
|
|
4710
|
+
);
|
|
4711
|
+
} else {
|
|
4712
|
+
data[columnTitle] = templateUtilities.renderTextAsHtml(
|
|
4713
|
+
columnData.text?.div ?? ""
|
|
4714
|
+
);
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
if (data["Advanced Directive Name"]?.toLowerCase() === "unknown") {
|
|
4719
|
+
continue;
|
|
4720
|
+
}
|
|
4721
|
+
isSummaryCreated = true;
|
|
4722
|
+
html += `
|
|
4723
|
+
<tr>
|
|
4724
|
+
<td>${templateUtilities.capitalizeFirstLetter(data["Advanced Directive Name"] ?? "")}</td>
|
|
4725
|
+
<td>${data["codeSystem"] ?? ""}</td>
|
|
4726
|
+
<td>${data["Provision Action"] ?? ""}</td>
|
|
4727
|
+
<td>${data["Policy Rule"] ?? ""}</td>
|
|
4728
|
+
<td>${data["DateTime"] ?? ""}</td>
|
|
4729
|
+
<td>${data["Source"] ?? ""}</td>
|
|
4730
|
+
</tr>`;
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
html += `
|
|
4734
|
+
</tbody>
|
|
4735
|
+
</table>
|
|
4736
|
+
</div>`;
|
|
4737
|
+
return isSummaryCreated ? html : void 0;
|
|
4452
4738
|
}
|
|
4453
4739
|
/**
|
|
4454
4740
|
* Internal static implementation that actually generates the narrative
|
|
@@ -4456,7 +4742,6 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
4456
4742
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
4457
4743
|
* @returns HTML string for rendering
|
|
4458
4744
|
*/
|
|
4459
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
4460
4745
|
static generateStaticNarrative(resources, timezone) {
|
|
4461
4746
|
const templateUtilities = new TemplateUtilities(resources);
|
|
4462
4747
|
let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
|
|
@@ -4464,35 +4749,57 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
4464
4749
|
html += `<table>
|
|
4465
4750
|
<thead>
|
|
4466
4751
|
<tr>
|
|
4467
|
-
<th>
|
|
4468
|
-
<th>
|
|
4752
|
+
<th>Directive</th>
|
|
4753
|
+
<th>Code (System)</th>
|
|
4469
4754
|
<th>Action Controlled</th>
|
|
4755
|
+
<th>Policy Rule</th>
|
|
4470
4756
|
<th>Date</th>
|
|
4471
4757
|
<th>Source</th>
|
|
4472
4758
|
</tr>
|
|
4473
4759
|
</thead>
|
|
4474
4760
|
<tbody>`;
|
|
4475
|
-
|
|
4761
|
+
const consentAdded = /* @__PURE__ */ new Set();
|
|
4476
4762
|
for (const resourceItem of resources) {
|
|
4477
4763
|
const consent = resourceItem;
|
|
4478
|
-
|
|
4479
|
-
|
|
4764
|
+
let consentDirective = {};
|
|
4765
|
+
for (const category of consent.category || []) {
|
|
4766
|
+
for (const coding of category.coding || []) {
|
|
4767
|
+
if (coding.system === ADVANCED_DIRECTIVE_CATEGORY_SYSTEM && coding.code && coding.code in ADVANCED_DIRECTIVE_CATEGORY_CODES) {
|
|
4768
|
+
consentDirective = {
|
|
4769
|
+
name: ADVANCED_DIRECTIVE_CATEGORY_CODES[coding.code],
|
|
4770
|
+
system: coding.system,
|
|
4771
|
+
code: coding.code
|
|
4772
|
+
};
|
|
4773
|
+
break;
|
|
4774
|
+
} else if (coding.system === LOINC_SYSTEM && coding.code && coding.code in ADVANCED_DIRECTIVE_LOINC_CODES) {
|
|
4775
|
+
consentDirective = {
|
|
4776
|
+
name: ADVANCED_DIRECTIVE_LOINC_CODES[coding.code],
|
|
4777
|
+
system: coding.system,
|
|
4778
|
+
code: coding.code
|
|
4779
|
+
};
|
|
4780
|
+
break;
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
if (!consentDirective.name || !consentDirective.code || !consentDirective.system || consentDirective.name.toLowerCase() === "unknown" || consentAdded.has(consentDirective.name)) {
|
|
4480
4785
|
continue;
|
|
4481
4786
|
}
|
|
4482
|
-
|
|
4787
|
+
consentAdded.add(consentDirective.name);
|
|
4788
|
+
const consentCodeSystem = `${consentDirective.code} (${codingSystemDisplayNames_default[consentDirective.system] || consentDirective.system})`;
|
|
4483
4789
|
html += `
|
|
4484
4790
|
<tr>
|
|
4485
|
-
<td>${
|
|
4486
|
-
<td>${
|
|
4791
|
+
<td>${consentDirective.name}</td>
|
|
4792
|
+
<td>${consentCodeSystem || ""}</td>
|
|
4487
4793
|
<td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
|
|
4488
|
-
<td>${consent.
|
|
4794
|
+
<td>${consent.policyRule ? templateUtilities.formatCodeableConceptValue(consent.policyRule) : ""}</td>
|
|
4795
|
+
<td>${templateUtilities.renderTime(consent.dateTime, timezone) || ""}</td>
|
|
4489
4796
|
<td>${templateUtilities.getOwnerTag(consent)}</td>
|
|
4490
4797
|
</tr>`;
|
|
4491
4798
|
}
|
|
4492
4799
|
html += `
|
|
4493
4800
|
</tbody>
|
|
4494
4801
|
</table>`;
|
|
4495
|
-
return
|
|
4802
|
+
return consentAdded.size > 0 ? html : void 0;
|
|
4496
4803
|
}
|
|
4497
4804
|
};
|
|
4498
4805
|
|