@imranq2/fhirpatientsummary 1.0.22 → 1.0.24
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 +627 -124
- package/dist/index.js +627 -124
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -81,6 +81,14 @@ var BLOOD_PRESSURE_LOINC_CODES = {
|
|
|
81
81
|
DIASTOLIC: "8462-4"
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
+
// src/structures/ips_section_constants.ts
|
|
85
|
+
var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
86
|
+
"Systolic Blood Pressure": "valueRatio.numerator.value",
|
|
87
|
+
"Diastolic Blood Pressure": "valueRatio.denominator.value",
|
|
88
|
+
"Default": "valueString"
|
|
89
|
+
};
|
|
90
|
+
var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
|
|
91
|
+
|
|
84
92
|
// src/structures/ips_section_resource_map.ts
|
|
85
93
|
var IPSSectionResourceFilters = {
|
|
86
94
|
// Patient section: only Patient resource
|
|
@@ -88,7 +96,7 @@ var IPSSectionResourceFilters = {
|
|
|
88
96
|
// Only include allergies
|
|
89
97
|
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "AllergyIntolerance",
|
|
90
98
|
// includes MedicationRequest, MedicationStatement. Medication is needed for medication names
|
|
91
|
-
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement"
|
|
99
|
+
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement"].includes(resource.resourceType) && resource.status === "active" || resource.resourceType === "Medication",
|
|
92
100
|
// Only include active conditions
|
|
93
101
|
["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
|
|
94
102
|
// Only include completed immunizations
|
|
@@ -115,10 +123,13 @@ var IPSSectionResourceFilters = {
|
|
|
115
123
|
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
|
|
116
124
|
};
|
|
117
125
|
var IPSSectionSummaryCompositionFilter = {
|
|
118
|
-
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system ===
|
|
119
|
-
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system ===
|
|
120
|
-
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system ===
|
|
121
|
-
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system ===
|
|
126
|
+
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "allergy_summary_document"),
|
|
127
|
+
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
|
|
128
|
+
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
|
|
129
|
+
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
|
|
130
|
+
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
|
|
131
|
+
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && ["lab_summary_document", "diagnosticreportlab_summary_document"].includes(c.code)),
|
|
132
|
+
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
|
|
122
133
|
};
|
|
123
134
|
var IPSSectionResourceHelper = class {
|
|
124
135
|
static getResourceFilterForSection(section) {
|
|
@@ -822,9 +833,11 @@ var TemplateUtilities = class {
|
|
|
822
833
|
if (dateValue instanceof Date) {
|
|
823
834
|
dateTime = DateTime.fromJSDate(dateValue);
|
|
824
835
|
} else if (typeof dateValue === "string") {
|
|
825
|
-
dateTime = DateTime.fromISO(dateValue);
|
|
826
836
|
if (!dateValue.includes("T")) {
|
|
827
837
|
dateOnly = true;
|
|
838
|
+
dateTime = DateTime.fromISO(dateValue, { zone: "utc" });
|
|
839
|
+
} else {
|
|
840
|
+
dateTime = DateTime.fromISO(dateValue);
|
|
828
841
|
}
|
|
829
842
|
} else {
|
|
830
843
|
dateTime = DateTime.fromISO(String(dateValue));
|
|
@@ -926,6 +939,9 @@ var TemplateUtilities = class {
|
|
|
926
939
|
}
|
|
927
940
|
};
|
|
928
941
|
|
|
942
|
+
// src/constants.ts
|
|
943
|
+
var ADDRESS_SIMILARITY_THRESHOLD = 70;
|
|
944
|
+
|
|
929
945
|
// src/narratives/templates/typescript/PatientTemplate.ts
|
|
930
946
|
var PatientTemplate = class _PatientTemplate {
|
|
931
947
|
/**
|
|
@@ -952,7 +968,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
952
968
|
<li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
|
|
953
969
|
<li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
|
|
954
970
|
<li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
|
|
955
|
-
<li><strong>Identifier(s):</strong>${this.renderIdentifiers(combinedPatient)}</li>
|
|
956
971
|
<li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
|
|
957
972
|
<li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
|
|
958
973
|
<li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus?.text || ""}</li>
|
|
@@ -973,7 +988,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
973
988
|
}
|
|
974
989
|
const combined = patients[0];
|
|
975
990
|
const allNames = [];
|
|
976
|
-
const allIdentifiers = [];
|
|
977
991
|
const allTelecom = [];
|
|
978
992
|
const allAddresses = [];
|
|
979
993
|
const allCommunication = [];
|
|
@@ -981,9 +995,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
981
995
|
if (patient.name) {
|
|
982
996
|
allNames.push(...patient.name);
|
|
983
997
|
}
|
|
984
|
-
if (patient.identifier) {
|
|
985
|
-
allIdentifiers.push(...patient.identifier);
|
|
986
|
-
}
|
|
987
998
|
if (patient.telecom) {
|
|
988
999
|
allTelecom.push(...patient.telecom);
|
|
989
1000
|
}
|
|
@@ -1010,7 +1021,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1010
1021
|
}
|
|
1011
1022
|
});
|
|
1012
1023
|
combined.name = allNames;
|
|
1013
|
-
combined.identifier = allIdentifiers;
|
|
1014
1024
|
combined.telecom = allTelecom;
|
|
1015
1025
|
combined.address = allAddresses;
|
|
1016
1026
|
combined.communication = allCommunication;
|
|
@@ -1036,21 +1046,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1036
1046
|
});
|
|
1037
1047
|
return Array.from(uniqueNames).map((nameText) => `<ul><li>${nameText}</li></ul>`).join("");
|
|
1038
1048
|
}
|
|
1039
|
-
/**
|
|
1040
|
-
* Renders patient identifiers as HTML list items
|
|
1041
|
-
* @param patient - Patient resources
|
|
1042
|
-
* @returns HTML string of list items
|
|
1043
|
-
*/
|
|
1044
|
-
static renderIdentifiers(patient) {
|
|
1045
|
-
if (!patient.identifier || patient.identifier.length === 0) {
|
|
1046
|
-
return "";
|
|
1047
|
-
}
|
|
1048
|
-
return patient.identifier.map((id) => {
|
|
1049
|
-
const system = id.system || "";
|
|
1050
|
-
const value = id.value || "";
|
|
1051
|
-
return `<ul><li>${system}: ${value}</li></ul>`;
|
|
1052
|
-
}).join("");
|
|
1053
|
-
}
|
|
1054
1049
|
/**
|
|
1055
1050
|
* Renders patient telecom information grouped by system
|
|
1056
1051
|
* @param patient - Patient resources
|
|
@@ -1152,7 +1147,89 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1152
1147
|
uniqueAddresses.add(addressText);
|
|
1153
1148
|
}
|
|
1154
1149
|
});
|
|
1155
|
-
|
|
1150
|
+
const deduplicatedAddresses = this.deduplicateSimilarAddresses(Array.from(uniqueAddresses));
|
|
1151
|
+
return deduplicatedAddresses.map((addressText) => `<ul><li>${addressText}</li></ul>`).join("");
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Calculates the similarity between two strings using Levenshtein distance
|
|
1155
|
+
* Returns a percentage (0-100) indicating how similar the strings are
|
|
1156
|
+
* @param str1 - First string
|
|
1157
|
+
* @param str2 - Second string
|
|
1158
|
+
* @returns Similarity percentage (0-100)
|
|
1159
|
+
*/
|
|
1160
|
+
static calculateStringSimilarity(str1, str2) {
|
|
1161
|
+
const longer = str1.length > str2.length ? str1 : str2;
|
|
1162
|
+
const shorter = str1.length > str2.length ? str2 : str1;
|
|
1163
|
+
if (longer.length === 0) {
|
|
1164
|
+
return 100;
|
|
1165
|
+
}
|
|
1166
|
+
const editDistance = this.levenshteinDistance(longer.toLowerCase(), shorter.toLowerCase());
|
|
1167
|
+
return (longer.length - editDistance) / longer.length * 100;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Calculates the Levenshtein distance between two strings
|
|
1171
|
+
* @param str1 - First string
|
|
1172
|
+
* @param str2 - Second string
|
|
1173
|
+
* @returns The Levenshtein distance
|
|
1174
|
+
*/
|
|
1175
|
+
static levenshteinDistance(str1, str2) {
|
|
1176
|
+
const matrix = [];
|
|
1177
|
+
for (let i = 0; i <= str2.length; i++) {
|
|
1178
|
+
matrix[i] = [i];
|
|
1179
|
+
}
|
|
1180
|
+
for (let j = 0; j <= str1.length; j++) {
|
|
1181
|
+
matrix[0][j] = j;
|
|
1182
|
+
}
|
|
1183
|
+
for (let i = 1; i <= str2.length; i++) {
|
|
1184
|
+
for (let j = 1; j <= str1.length; j++) {
|
|
1185
|
+
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
1186
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1187
|
+
} else {
|
|
1188
|
+
matrix[i][j] = Math.min(
|
|
1189
|
+
matrix[i - 1][j - 1] + 1,
|
|
1190
|
+
// substitution
|
|
1191
|
+
matrix[i][j - 1] + 1,
|
|
1192
|
+
// insertion
|
|
1193
|
+
matrix[i - 1][j] + 1
|
|
1194
|
+
// deletion
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return matrix[str2.length][str1.length];
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Deduplicates addresses that are more than ADDRESS_SIMILARITY_THRESHOLD% similar
|
|
1203
|
+
* @param addresses - Array of address strings
|
|
1204
|
+
* @returns Array of deduplicated addresses
|
|
1205
|
+
*/
|
|
1206
|
+
static deduplicateSimilarAddresses(addresses) {
|
|
1207
|
+
if (addresses.length <= 1) {
|
|
1208
|
+
return addresses;
|
|
1209
|
+
}
|
|
1210
|
+
const deduplicated = [];
|
|
1211
|
+
const processed = /* @__PURE__ */ new Set();
|
|
1212
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
1213
|
+
if (processed.has(i)) {
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
let keepAddress = addresses[i];
|
|
1217
|
+
processed.add(i);
|
|
1218
|
+
for (let j = i + 1; j < addresses.length; j++) {
|
|
1219
|
+
if (processed.has(j)) {
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
const similarity = this.calculateStringSimilarity(addresses[i], addresses[j]);
|
|
1223
|
+
if (similarity > ADDRESS_SIMILARITY_THRESHOLD) {
|
|
1224
|
+
processed.add(j);
|
|
1225
|
+
if (addresses[j].length > keepAddress.length) {
|
|
1226
|
+
keepAddress = addresses[j];
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
deduplicated.push(keepAddress);
|
|
1231
|
+
}
|
|
1232
|
+
return deduplicated;
|
|
1156
1233
|
}
|
|
1157
1234
|
/**
|
|
1158
1235
|
* Renders patient deceased status
|
|
@@ -1400,6 +1477,75 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1400
1477
|
generateNarrative(resources, timezone) {
|
|
1401
1478
|
return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
|
|
1402
1479
|
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Generate HTML narrative for Medication resources using summary
|
|
1482
|
+
* @param resources - FHIR Composition resources
|
|
1483
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1484
|
+
* @returns HTML string for rendering
|
|
1485
|
+
*/
|
|
1486
|
+
generateSummaryNarrative(resources, timezone) {
|
|
1487
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1488
|
+
let html = `
|
|
1489
|
+
<div>
|
|
1490
|
+
<table>
|
|
1491
|
+
<thead>
|
|
1492
|
+
<tr>
|
|
1493
|
+
<th>Medication</th>
|
|
1494
|
+
<th>Sig</th>
|
|
1495
|
+
<th>Days of Supply</th>
|
|
1496
|
+
<th>Refills</th>
|
|
1497
|
+
<th>Start Date</th>
|
|
1498
|
+
</tr>
|
|
1499
|
+
</thead>
|
|
1500
|
+
<tbody>`;
|
|
1501
|
+
for (const resourceItem of resources) {
|
|
1502
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
1503
|
+
const data = {};
|
|
1504
|
+
for (const columnData of rowData.section ?? []) {
|
|
1505
|
+
switch (columnData.title) {
|
|
1506
|
+
case "Medication Name":
|
|
1507
|
+
data["medication"] = columnData.text?.div ?? "";
|
|
1508
|
+
break;
|
|
1509
|
+
case "Status":
|
|
1510
|
+
data["status"] = columnData.text?.div ?? "";
|
|
1511
|
+
break;
|
|
1512
|
+
case "Prescriber Instruction":
|
|
1513
|
+
data["sig-prescriber"] = columnData.text?.div ?? "";
|
|
1514
|
+
break;
|
|
1515
|
+
case "Pharmacy Instruction":
|
|
1516
|
+
data["sig-pharmacy"] = columnData.text?.div ?? "";
|
|
1517
|
+
break;
|
|
1518
|
+
case "Days Of Supply":
|
|
1519
|
+
data["daysOfSupply"] = columnData.text?.div ?? "";
|
|
1520
|
+
break;
|
|
1521
|
+
case "Refills Remaining":
|
|
1522
|
+
data["refills"] = columnData.text?.div ?? "";
|
|
1523
|
+
break;
|
|
1524
|
+
case "Authored On Date":
|
|
1525
|
+
data["startDate"] = columnData.text?.div ?? "";
|
|
1526
|
+
break;
|
|
1527
|
+
default:
|
|
1528
|
+
break;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (data["status"] === "active") {
|
|
1532
|
+
html += `
|
|
1533
|
+
<tr>
|
|
1534
|
+
<td>${data["medication"]}</td>
|
|
1535
|
+
<td>${data["sig-prescriber"] || data["sig-pharmacy"]}</td>
|
|
1536
|
+
<td>${data["daysOfSupply"]}</td>
|
|
1537
|
+
<td>${data["refills"]}</td>
|
|
1538
|
+
<td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
|
|
1539
|
+
</tr>`;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
html += `
|
|
1544
|
+
</tbody>
|
|
1545
|
+
</table>
|
|
1546
|
+
</div>`;
|
|
1547
|
+
return html;
|
|
1548
|
+
}
|
|
1403
1549
|
/**
|
|
1404
1550
|
* Safely parse a date string and return a valid Date object or null
|
|
1405
1551
|
* @param dateString - The date string to parse
|
|
@@ -1412,55 +1558,6 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1412
1558
|
const date = new Date(dateString);
|
|
1413
1559
|
return !isNaN(date.getTime()) ? date : null;
|
|
1414
1560
|
}
|
|
1415
|
-
/**
|
|
1416
|
-
* Determine if a MedicationRequest is active
|
|
1417
|
-
* @param medicationRequest - The MedicationRequest resource
|
|
1418
|
-
* @returns boolean indicating if the medication request is active
|
|
1419
|
-
*/
|
|
1420
|
-
static isActiveMedicationRequest(medicationRequest) {
|
|
1421
|
-
const status = medicationRequest.status?.toLowerCase();
|
|
1422
|
-
if (status === "active" || status === "unknown") {
|
|
1423
|
-
return true;
|
|
1424
|
-
}
|
|
1425
|
-
if (status === "completed" || status === "cancelled" || status === "stopped" || status === "draft") {
|
|
1426
|
-
return false;
|
|
1427
|
-
}
|
|
1428
|
-
const endDate = medicationRequest.dispenseRequest?.validityPeriod?.end;
|
|
1429
|
-
if (!endDate) {
|
|
1430
|
-
return true;
|
|
1431
|
-
}
|
|
1432
|
-
const parsedEndDate = this.parseDate(endDate);
|
|
1433
|
-
if (!parsedEndDate) {
|
|
1434
|
-
return true;
|
|
1435
|
-
}
|
|
1436
|
-
return parsedEndDate.getTime() > Date.now();
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* Determine if a MedicationStatement is active
|
|
1440
|
-
* @param medicationStatement - The MedicationStatement resource
|
|
1441
|
-
* @returns boolean indicating if the medication statement is active
|
|
1442
|
-
*/
|
|
1443
|
-
static isActiveMedicationStatement(medicationStatement) {
|
|
1444
|
-
const status = medicationStatement.status?.toLowerCase();
|
|
1445
|
-
if (status === "active" || status === "intended" || status === "unknown") {
|
|
1446
|
-
return true;
|
|
1447
|
-
}
|
|
1448
|
-
if (status === "completed" || status === "stopped" || status === "not-taken") {
|
|
1449
|
-
return false;
|
|
1450
|
-
}
|
|
1451
|
-
let endDate;
|
|
1452
|
-
if (medicationStatement.effectivePeriod?.end) {
|
|
1453
|
-
endDate = medicationStatement.effectivePeriod.end;
|
|
1454
|
-
}
|
|
1455
|
-
if (!endDate) {
|
|
1456
|
-
return true;
|
|
1457
|
-
}
|
|
1458
|
-
const parsedEndDate = this.parseDate(endDate);
|
|
1459
|
-
if (!parsedEndDate) {
|
|
1460
|
-
return true;
|
|
1461
|
-
}
|
|
1462
|
-
return parsedEndDate.getTime() > Date.now();
|
|
1463
|
-
}
|
|
1464
1561
|
/**
|
|
1465
1562
|
* Internal static implementation that actually generates the narrative
|
|
1466
1563
|
* @param resources - FHIR Medication resources
|
|
@@ -1474,20 +1571,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1474
1571
|
const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
|
|
1475
1572
|
const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
|
|
1476
1573
|
const allActiveMedications = [];
|
|
1477
|
-
const allInactiveMedications = [];
|
|
1478
1574
|
medicationRequests.forEach((mr) => {
|
|
1479
|
-
|
|
1480
|
-
allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
|
|
1481
|
-
} else {
|
|
1482
|
-
allInactiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
|
|
1483
|
-
}
|
|
1575
|
+
allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
|
|
1484
1576
|
});
|
|
1485
1577
|
medicationStatements.forEach((ms) => {
|
|
1486
|
-
|
|
1487
|
-
allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
|
|
1488
|
-
} else {
|
|
1489
|
-
allInactiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
|
|
1490
|
-
}
|
|
1578
|
+
allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
|
|
1491
1579
|
});
|
|
1492
1580
|
const sortMedications = (medications) => {
|
|
1493
1581
|
medications.sort((a, b) => {
|
|
@@ -1517,11 +1605,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1517
1605
|
};
|
|
1518
1606
|
if (allActiveMedications.length > 0) {
|
|
1519
1607
|
sortMedications(allActiveMedications);
|
|
1520
|
-
html += this.renderCombinedMedications(templateUtilities, allActiveMedications
|
|
1521
|
-
}
|
|
1522
|
-
if (allInactiveMedications.length > 0) {
|
|
1523
|
-
sortMedications(allInactiveMedications);
|
|
1524
|
-
html += this.renderCombinedMedications(templateUtilities, allInactiveMedications, false);
|
|
1608
|
+
html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
|
|
1525
1609
|
}
|
|
1526
1610
|
return html;
|
|
1527
1611
|
}
|
|
@@ -1559,12 +1643,10 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1559
1643
|
* Render HTML table for combined MedicationRequest and MedicationStatement resources
|
|
1560
1644
|
* @param templateUtilities - Instance of TemplateUtilities for utility functions
|
|
1561
1645
|
* @param medications - Array of combined medication resources
|
|
1562
|
-
* @param sectionTitle - Title for the section
|
|
1563
1646
|
* @returns HTML string for rendering
|
|
1564
1647
|
*/
|
|
1565
|
-
static renderCombinedMedications(templateUtilities, medications
|
|
1648
|
+
static renderCombinedMedications(templateUtilities, medications) {
|
|
1566
1649
|
let html = `
|
|
1567
|
-
<h3>${isActiveSection ? "Active Medications" : "Inactive Medications"}</h3>
|
|
1568
1650
|
<table>
|
|
1569
1651
|
<thead>
|
|
1570
1652
|
<tr>
|
|
@@ -1573,9 +1655,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1573
1655
|
<th>Sig</th>
|
|
1574
1656
|
<th>Dispense Quantity</th>
|
|
1575
1657
|
<th>Refills</th>
|
|
1576
|
-
<th>Start Date</th
|
|
1577
|
-
<th>End Date</th>`}
|
|
1578
|
-
<th>Status</th>
|
|
1658
|
+
<th>Start Date</th>
|
|
1579
1659
|
</tr>
|
|
1580
1660
|
</thead>
|
|
1581
1661
|
<tbody>`;
|
|
@@ -1587,12 +1667,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1587
1667
|
let dispenseQuantity = "-";
|
|
1588
1668
|
let refills = "-";
|
|
1589
1669
|
let startDate = "-";
|
|
1590
|
-
let endDate = "-";
|
|
1591
|
-
let status;
|
|
1592
1670
|
if (medication.type === "request") {
|
|
1593
1671
|
const mr = medication.resource;
|
|
1594
1672
|
type = "Request";
|
|
1595
|
-
status = mr.status ? String(mr.status) : "-";
|
|
1596
1673
|
medicationName = templateUtilities.getMedicationName(
|
|
1597
1674
|
mr.medicationReference || mr.medicationCodeableConcept
|
|
1598
1675
|
);
|
|
@@ -1606,14 +1683,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1606
1683
|
refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "-";
|
|
1607
1684
|
if (mr.dispenseRequest?.validityPeriod) {
|
|
1608
1685
|
startDate = mr.dispenseRequest.validityPeriod.start || "-";
|
|
1609
|
-
endDate = mr.dispenseRequest.validityPeriod.end || "-";
|
|
1610
1686
|
} else {
|
|
1611
1687
|
startDate = mr.authoredOn || "-";
|
|
1612
1688
|
}
|
|
1613
1689
|
} else {
|
|
1614
1690
|
const ms = medication.resource;
|
|
1615
1691
|
type = "Statement";
|
|
1616
|
-
status = ms.status ? String(ms.status) : "-";
|
|
1617
1692
|
medicationName = templateUtilities.getMedicationName(
|
|
1618
1693
|
ms.medicationReference || ms.medicationCodeableConcept
|
|
1619
1694
|
);
|
|
@@ -1622,7 +1697,6 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1622
1697
|
startDate = ms.effectiveDateTime;
|
|
1623
1698
|
} else if (ms.effectivePeriod) {
|
|
1624
1699
|
startDate = ms.effectivePeriod.start || "-";
|
|
1625
|
-
endDate = ms.effectivePeriod.end || "-";
|
|
1626
1700
|
}
|
|
1627
1701
|
}
|
|
1628
1702
|
html += `
|
|
@@ -1632,9 +1706,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1632
1706
|
<td>${sig}</td>
|
|
1633
1707
|
<td>${dispenseQuantity}</td>
|
|
1634
1708
|
<td>${refills}</td>
|
|
1635
|
-
<td>${startDate}</td
|
|
1636
|
-
<td>${endDate}</td>`}
|
|
1637
|
-
<td>${status}</td>
|
|
1709
|
+
<td>${startDate}</td>
|
|
1638
1710
|
</tr>`;
|
|
1639
1711
|
}
|
|
1640
1712
|
html += `
|
|
@@ -1775,6 +1847,7 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1775
1847
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1776
1848
|
* @returns HTML string for rendering
|
|
1777
1849
|
*/
|
|
1850
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1778
1851
|
static generateStaticNarrative(resources, timezone) {
|
|
1779
1852
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1780
1853
|
let html = ``;
|
|
@@ -1791,7 +1864,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1791
1864
|
<th>Problem</th>
|
|
1792
1865
|
<th>Onset Date</th>
|
|
1793
1866
|
<th>Recorded Date</th>
|
|
1794
|
-
<th>Notes</th>
|
|
1795
1867
|
</tr>
|
|
1796
1868
|
</thead>
|
|
1797
1869
|
<tbody>`;
|
|
@@ -1800,7 +1872,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1800
1872
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
1801
1873
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
1802
1874
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
1803
|
-
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
|
|
1804
1875
|
</tr>`;
|
|
1805
1876
|
}
|
|
1806
1877
|
html += `</tbody>
|
|
@@ -1809,13 +1880,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1809
1880
|
}
|
|
1810
1881
|
};
|
|
1811
1882
|
|
|
1812
|
-
// src/structures/ips_section_constants.ts
|
|
1813
|
-
var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
1814
|
-
"Systolic Blood Pressure": "valueRatio.numerator.value",
|
|
1815
|
-
"Diastolic Blood Pressure": "valueRatio.denominator.value",
|
|
1816
|
-
"Default": "valueString"
|
|
1817
|
-
};
|
|
1818
|
-
|
|
1819
1883
|
// src/narratives/templates/typescript/VitalSignsTemplate.ts
|
|
1820
1884
|
var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
1821
1885
|
/**
|
|
@@ -1998,6 +2062,400 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
1998
2062
|
generateNarrative(resources, timezone) {
|
|
1999
2063
|
return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
|
|
2000
2064
|
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Helper function to format observation data fields
|
|
2067
|
+
* @param obsData - Record containing observation data fields
|
|
2068
|
+
*/
|
|
2069
|
+
formatSummaryObservationData(obsData) {
|
|
2070
|
+
const valueType = obsData["valueType"];
|
|
2071
|
+
switch (valueType) {
|
|
2072
|
+
case "valueQuantity":
|
|
2073
|
+
if (obsData["value"] && obsData["unit"]) {
|
|
2074
|
+
obsData["formattedValue"] = `${obsData["value"]} ${obsData["unit"]}`;
|
|
2075
|
+
} else if (obsData["value"]) {
|
|
2076
|
+
obsData["formattedValue"] = obsData["value"];
|
|
2077
|
+
}
|
|
2078
|
+
break;
|
|
2079
|
+
case "valueCodeableConcept":
|
|
2080
|
+
case "valueString":
|
|
2081
|
+
case "valueBoolean":
|
|
2082
|
+
case "valueInteger":
|
|
2083
|
+
case "valueDateTime":
|
|
2084
|
+
case "valueTime":
|
|
2085
|
+
obsData["formattedValue"] = obsData["value"] ?? "";
|
|
2086
|
+
break;
|
|
2087
|
+
case "valuePeriod":
|
|
2088
|
+
if (obsData["valuePeriodStart"] && obsData["valuePeriodEnd"]) {
|
|
2089
|
+
obsData["formattedValue"] = `${obsData["valuePeriodStart"]} - ${obsData["valuePeriodEnd"]}`;
|
|
2090
|
+
} else if (obsData["valuePeriodStart"]) {
|
|
2091
|
+
obsData["formattedValue"] = `From ${obsData["valuePeriodStart"]}`;
|
|
2092
|
+
} else if (obsData["valuePeriodEnd"]) {
|
|
2093
|
+
obsData["formattedValue"] = `Until ${obsData["valuePeriodEnd"]}`;
|
|
2094
|
+
}
|
|
2095
|
+
break;
|
|
2096
|
+
case "valueSampledData": {
|
|
2097
|
+
const sampledParts = [];
|
|
2098
|
+
if (obsData["sampledDataOriginValue"]) {
|
|
2099
|
+
sampledParts.push(`Origin: ${obsData["sampledDataOriginValue"]}${obsData["sampledDataOriginUnit"] ? " " + obsData["sampledDataOriginUnit"] : ""}`);
|
|
2100
|
+
}
|
|
2101
|
+
if (obsData["sampledDataPeriod"]) {
|
|
2102
|
+
sampledParts.push(`Period: ${obsData["sampledDataPeriod"]}`);
|
|
2103
|
+
}
|
|
2104
|
+
if (obsData["sampledDataFactor"]) {
|
|
2105
|
+
sampledParts.push(`Factor: ${obsData["sampledDataFactor"]}`);
|
|
2106
|
+
}
|
|
2107
|
+
if (obsData["sampledDataLowerLimit"]) {
|
|
2108
|
+
sampledParts.push(`Lower: ${obsData["sampledDataLowerLimit"]}`);
|
|
2109
|
+
}
|
|
2110
|
+
if (obsData["sampledDataUpperLimit"]) {
|
|
2111
|
+
sampledParts.push(`Upper: ${obsData["sampledDataUpperLimit"]}`);
|
|
2112
|
+
}
|
|
2113
|
+
if (obsData["sampledDataData"]) {
|
|
2114
|
+
sampledParts.push(`Data: ${obsData["sampledDataData"]}`);
|
|
2115
|
+
}
|
|
2116
|
+
obsData["formattedValue"] = sampledParts.join(", ");
|
|
2117
|
+
break;
|
|
2118
|
+
}
|
|
2119
|
+
case "valueRange": {
|
|
2120
|
+
const rangeParts = [];
|
|
2121
|
+
if (obsData["valueRangeLowValue"]) {
|
|
2122
|
+
rangeParts.push(`${obsData["valueRangeLowValue"]}${obsData["valueRangeLowUnit"] ? " " + obsData["valueRangeLowUnit"] : ""}`);
|
|
2123
|
+
}
|
|
2124
|
+
if (obsData["valueRangeHighValue"]) {
|
|
2125
|
+
rangeParts.push(`${obsData["valueRangeHighValue"]}${obsData["valueRangeHighUnit"] ? " " + obsData["valueRangeHighUnit"] : ""}`);
|
|
2126
|
+
}
|
|
2127
|
+
obsData["formattedValue"] = rangeParts.join(" - ");
|
|
2128
|
+
break;
|
|
2129
|
+
}
|
|
2130
|
+
case "valueRatio": {
|
|
2131
|
+
const numerator = obsData["valueRatioNumeratorValue"] ? `${obsData["valueRatioNumeratorValue"]}${obsData["valueRatioNumeratorUnit"] ? " " + obsData["valueRatioNumeratorUnit"] : ""}` : "";
|
|
2132
|
+
const denominator = obsData["valueRatioDenominatorValue"] ? `${obsData["valueRatioDenominatorValue"]}${obsData["valueRatioDenominatorUnit"] ? " " + obsData["valueRatioDenominatorUnit"] : ""}` : "";
|
|
2133
|
+
if (numerator && denominator) {
|
|
2134
|
+
obsData["formattedValue"] = `${numerator} / ${denominator}`;
|
|
2135
|
+
} else if (numerator) {
|
|
2136
|
+
obsData["formattedValue"] = numerator;
|
|
2137
|
+
}
|
|
2138
|
+
break;
|
|
2139
|
+
}
|
|
2140
|
+
default:
|
|
2141
|
+
obsData["formattedValue"] = obsData["value"] ?? "";
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
if (obsData["referenceRangeLow"]) {
|
|
2145
|
+
obsData["referenceRange"] = obsData["referenceRangeLow"] + " " + obsData["referenceRangeLowUnit"];
|
|
2146
|
+
}
|
|
2147
|
+
if (obsData["referenceRangeHigh"]) {
|
|
2148
|
+
if (obsData["referenceRange"]) {
|
|
2149
|
+
obsData["referenceRange"] += " - ";
|
|
2150
|
+
} else {
|
|
2151
|
+
obsData["referenceRange"] = "";
|
|
2152
|
+
}
|
|
2153
|
+
obsData["referenceRange"] += obsData["referenceRangeHigh"] + " " + obsData["referenceRangeHighUnit"];
|
|
2154
|
+
}
|
|
2155
|
+
if (obsData["referenceRangeAgeLowValue"] || obsData["referenceRangeAgeHighValue"]) {
|
|
2156
|
+
const ageParts = [];
|
|
2157
|
+
if (obsData["referenceRangeAgeLowValue"]) {
|
|
2158
|
+
ageParts.push(`${obsData["referenceRangeAgeLowValue"]}${obsData["referenceRangeAgeLowUnit"] ? " " + obsData["referenceRangeAgeLowUnit"] : ""}`);
|
|
2159
|
+
}
|
|
2160
|
+
if (obsData["referenceRangeAgeHighValue"]) {
|
|
2161
|
+
ageParts.push(`${obsData["referenceRangeAgeHighValue"]}${obsData["referenceRangeAgeHighUnit"] ? " " + obsData["referenceRangeAgeHighUnit"] : ""}`);
|
|
2162
|
+
}
|
|
2163
|
+
if (obsData["referenceRange"]) {
|
|
2164
|
+
obsData["referenceRange"] += ` (Age: ${ageParts.join(" - ")})`;
|
|
2165
|
+
} else {
|
|
2166
|
+
obsData["referenceRange"] = `Age: ${ageParts.join(" - ")}`;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Helper function to extract observation field data
|
|
2172
|
+
* @param column - Column data from the summary
|
|
2173
|
+
* @param targetData - Record to populate with extracted data
|
|
2174
|
+
*/
|
|
2175
|
+
extractSummaryObservationFields(column, targetData) {
|
|
2176
|
+
switch (column.title) {
|
|
2177
|
+
case "Labs Name":
|
|
2178
|
+
targetData["code"] = column.text?.div ?? "";
|
|
2179
|
+
break;
|
|
2180
|
+
case "effectiveDateTime":
|
|
2181
|
+
targetData["effectiveDateTime"] = column.text?.div ?? "";
|
|
2182
|
+
break;
|
|
2183
|
+
case "effectivePeriod.start":
|
|
2184
|
+
targetData["effectivePeriodStart"] = column.text?.div ?? "";
|
|
2185
|
+
break;
|
|
2186
|
+
case "effectivePeriod.end":
|
|
2187
|
+
targetData["effectivePeriodEnd"] = column.text?.div ?? "";
|
|
2188
|
+
break;
|
|
2189
|
+
// valueQuantity
|
|
2190
|
+
case "valueQuantity.value":
|
|
2191
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2192
|
+
targetData["valueType"] = "valueQuantity";
|
|
2193
|
+
break;
|
|
2194
|
+
case "valueQuantity.unit":
|
|
2195
|
+
targetData["unit"] = column.text?.div ?? "";
|
|
2196
|
+
break;
|
|
2197
|
+
// valueCodeableConcept
|
|
2198
|
+
case "valueCodeableConcept.text":
|
|
2199
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2200
|
+
targetData["valueType"] = "valueCodeableConcept";
|
|
2201
|
+
break;
|
|
2202
|
+
case "valueCodeableConcept.coding.display":
|
|
2203
|
+
if (!targetData["value"]) {
|
|
2204
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2205
|
+
targetData["valueType"] = "valueCodeableConcept";
|
|
2206
|
+
}
|
|
2207
|
+
break;
|
|
2208
|
+
// valueString
|
|
2209
|
+
case "valueString":
|
|
2210
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2211
|
+
targetData["valueType"] = "valueString";
|
|
2212
|
+
break;
|
|
2213
|
+
// valueBoolean
|
|
2214
|
+
case "valueBoolean":
|
|
2215
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2216
|
+
targetData["valueType"] = "valueBoolean";
|
|
2217
|
+
break;
|
|
2218
|
+
// valueInteger
|
|
2219
|
+
case "valueInteger":
|
|
2220
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2221
|
+
targetData["valueType"] = "valueInteger";
|
|
2222
|
+
break;
|
|
2223
|
+
// valueDateTime
|
|
2224
|
+
case "valueDateTime":
|
|
2225
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2226
|
+
targetData["valueType"] = "valueDateTime";
|
|
2227
|
+
break;
|
|
2228
|
+
// valuePeriod
|
|
2229
|
+
case "valuePeriod.start":
|
|
2230
|
+
targetData["valuePeriodStart"] = column.text?.div ?? "";
|
|
2231
|
+
targetData["valueType"] = "valuePeriod";
|
|
2232
|
+
break;
|
|
2233
|
+
case "valuePeriod.end":
|
|
2234
|
+
targetData["valuePeriodEnd"] = column.text?.div ?? "";
|
|
2235
|
+
targetData["valueType"] = "valuePeriod";
|
|
2236
|
+
break;
|
|
2237
|
+
// valueTime
|
|
2238
|
+
case "valueTime":
|
|
2239
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2240
|
+
targetData["valueType"] = "valueTime";
|
|
2241
|
+
break;
|
|
2242
|
+
// valueSampledData
|
|
2243
|
+
case "valueSampledData.origin.value":
|
|
2244
|
+
targetData["sampledDataOriginValue"] = column.text?.div ?? "";
|
|
2245
|
+
targetData["valueType"] = "valueSampledData";
|
|
2246
|
+
break;
|
|
2247
|
+
case "valueSampledData.origin.unit":
|
|
2248
|
+
targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
|
|
2249
|
+
break;
|
|
2250
|
+
case "valueSampledData.period":
|
|
2251
|
+
targetData["sampledDataPeriod"] = column.text?.div ?? "";
|
|
2252
|
+
break;
|
|
2253
|
+
case "valueSampledData.factor":
|
|
2254
|
+
targetData["sampledDataFactor"] = column.text?.div ?? "";
|
|
2255
|
+
break;
|
|
2256
|
+
case "valueSampledData.lowerLimit":
|
|
2257
|
+
targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
|
|
2258
|
+
break;
|
|
2259
|
+
case "valueSampledData.upperLimit":
|
|
2260
|
+
targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
|
|
2261
|
+
break;
|
|
2262
|
+
case "valueSampledData.data":
|
|
2263
|
+
targetData["sampledDataData"] = column.text?.div ?? "";
|
|
2264
|
+
break;
|
|
2265
|
+
// valueRange
|
|
2266
|
+
case "valueRange.low.value":
|
|
2267
|
+
targetData["valueRangeLowValue"] = column.text?.div ?? "";
|
|
2268
|
+
targetData["valueType"] = "valueRange";
|
|
2269
|
+
break;
|
|
2270
|
+
case "valueRange.low.unit":
|
|
2271
|
+
targetData["valueRangeLowUnit"] = column.text?.div ?? "";
|
|
2272
|
+
break;
|
|
2273
|
+
case "valueRange.high.value":
|
|
2274
|
+
targetData["valueRangeHighValue"] = column.text?.div ?? "";
|
|
2275
|
+
break;
|
|
2276
|
+
case "valueRange.high.unit":
|
|
2277
|
+
targetData["valueRangeHighUnit"] = column.text?.div ?? "";
|
|
2278
|
+
break;
|
|
2279
|
+
// valueRatio
|
|
2280
|
+
case "valueRatio.numerator.value":
|
|
2281
|
+
targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
|
|
2282
|
+
targetData["valueType"] = "valueRatio";
|
|
2283
|
+
break;
|
|
2284
|
+
case "valueRatio.numerator.unit":
|
|
2285
|
+
targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
|
|
2286
|
+
break;
|
|
2287
|
+
case "valueRatio.denominator.value":
|
|
2288
|
+
targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
|
|
2289
|
+
break;
|
|
2290
|
+
case "valueRatio.denominator.unit":
|
|
2291
|
+
targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
|
|
2292
|
+
break;
|
|
2293
|
+
// referenceRange
|
|
2294
|
+
case "referenceRange.low.value":
|
|
2295
|
+
targetData["referenceRangeLow"] = column.text?.div ?? "";
|
|
2296
|
+
break;
|
|
2297
|
+
case "referenceRange.low.unit":
|
|
2298
|
+
targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
|
|
2299
|
+
break;
|
|
2300
|
+
case "referenceRange.high.value":
|
|
2301
|
+
targetData["referenceRangeHigh"] = column.text?.div ?? "";
|
|
2302
|
+
break;
|
|
2303
|
+
case "referenceRange.high.unit":
|
|
2304
|
+
targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
|
|
2305
|
+
break;
|
|
2306
|
+
case "referenceRange.age.low.value":
|
|
2307
|
+
targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
|
|
2308
|
+
break;
|
|
2309
|
+
case "referenceRange.age.low.unit":
|
|
2310
|
+
targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
|
|
2311
|
+
break;
|
|
2312
|
+
case "referenceRange.age.high.value":
|
|
2313
|
+
targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
|
|
2314
|
+
break;
|
|
2315
|
+
case "referenceRange.age.high.unit":
|
|
2316
|
+
targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
|
|
2317
|
+
break;
|
|
2318
|
+
default:
|
|
2319
|
+
break;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
/**
|
|
2323
|
+
* Generate HTML narrative for Diagnostic Results & Observation resources using summary
|
|
2324
|
+
* @param resources - FHIR Composition resources
|
|
2325
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2326
|
+
* @returns HTML string for rendering
|
|
2327
|
+
*/
|
|
2328
|
+
generateSummaryNarrative(resources, timezone) {
|
|
2329
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2330
|
+
let html = `
|
|
2331
|
+
<div>`;
|
|
2332
|
+
let observationhtml = `
|
|
2333
|
+
<div>
|
|
2334
|
+
<h3>Observations</h3>
|
|
2335
|
+
<table>
|
|
2336
|
+
<thead>
|
|
2337
|
+
<tr>
|
|
2338
|
+
<th>Code</th>
|
|
2339
|
+
<th>Result</th>
|
|
2340
|
+
<th>Reference Range</th>
|
|
2341
|
+
<th>Date</th>
|
|
2342
|
+
</tr>
|
|
2343
|
+
</thead>
|
|
2344
|
+
<tbody>`;
|
|
2345
|
+
let diagnosticReporthtml = `
|
|
2346
|
+
<div>
|
|
2347
|
+
<h3>Diagnostic Reports</h3>
|
|
2348
|
+
<table>
|
|
2349
|
+
<thead>
|
|
2350
|
+
<tr>
|
|
2351
|
+
<th>Report</th>
|
|
2352
|
+
<th>Performer</th>
|
|
2353
|
+
<th>Issued</th>
|
|
2354
|
+
</tr>
|
|
2355
|
+
</thead>
|
|
2356
|
+
<tbody>`;
|
|
2357
|
+
let observationExists = false;
|
|
2358
|
+
let diagnosticReportExists = false;
|
|
2359
|
+
for (const resourceItem of resources) {
|
|
2360
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
2361
|
+
const data = {};
|
|
2362
|
+
const components = [];
|
|
2363
|
+
for (const columnData of rowData.section ?? []) {
|
|
2364
|
+
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
2365
|
+
if (columnData.text?.div === "Observation.component" && columnData.section) {
|
|
2366
|
+
for (const componentSection of columnData.section) {
|
|
2367
|
+
const componentData = {};
|
|
2368
|
+
for (const nestedColumn of componentSection.section ?? []) {
|
|
2369
|
+
this.extractSummaryObservationFields(nestedColumn, componentData);
|
|
2370
|
+
}
|
|
2371
|
+
if (Object.keys(componentData).length > 0) {
|
|
2372
|
+
components.push(componentData);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
} else {
|
|
2376
|
+
this.extractSummaryObservationFields(columnData, data);
|
|
2377
|
+
}
|
|
2378
|
+
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2379
|
+
switch (columnData.title) {
|
|
2380
|
+
case "Diagnostic Report Name":
|
|
2381
|
+
data["report"] = columnData.text?.div ?? "";
|
|
2382
|
+
break;
|
|
2383
|
+
case "Performer":
|
|
2384
|
+
data["performer"] = columnData.text?.div ?? "";
|
|
2385
|
+
break;
|
|
2386
|
+
case "Issued Date":
|
|
2387
|
+
data["issued"] = columnData.text?.div ?? "";
|
|
2388
|
+
break;
|
|
2389
|
+
case "Status":
|
|
2390
|
+
data["status"] = columnData.text?.div ?? "";
|
|
2391
|
+
break;
|
|
2392
|
+
default:
|
|
2393
|
+
break;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
2398
|
+
observationExists = true;
|
|
2399
|
+
let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
|
|
2400
|
+
if (!date && data["effectivePeriodStart"]) {
|
|
2401
|
+
date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
|
|
2402
|
+
if (data["effectivePeriodEnd"]) {
|
|
2403
|
+
date += " - " + templateUtilities.renderTime(data["effectivePeriodEnd"], timezone);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
if (components.length > 0) {
|
|
2407
|
+
const groupName = data["code"] ?? "";
|
|
2408
|
+
for (const component of components) {
|
|
2409
|
+
this.formatSummaryObservationData(component);
|
|
2410
|
+
observationhtml += `
|
|
2411
|
+
<tr>
|
|
2412
|
+
<td>${groupName ? groupName + " - " : ""}${component["code"] ?? "-"}</td>
|
|
2413
|
+
<td>${component["formattedValue"] ?? "-"}</td>
|
|
2414
|
+
<td>${component["referenceRange"]?.trim() ?? "-"}</td>
|
|
2415
|
+
<td>${date ?? "-"}</td>
|
|
2416
|
+
</tr>`;
|
|
2417
|
+
}
|
|
2418
|
+
} else {
|
|
2419
|
+
this.formatSummaryObservationData(data);
|
|
2420
|
+
observationhtml += `
|
|
2421
|
+
<tr>
|
|
2422
|
+
<td>${data["code"] ?? "-"}</td>
|
|
2423
|
+
<td>${data["formattedValue"] ?? "-"}</td>
|
|
2424
|
+
<td>${data["referenceRange"]?.trim() ?? "-"}</td>
|
|
2425
|
+
<td>${date ?? "-"}</td>
|
|
2426
|
+
</tr>`;
|
|
2427
|
+
}
|
|
2428
|
+
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2429
|
+
if (data["status"] === "final") {
|
|
2430
|
+
diagnosticReportExists = true;
|
|
2431
|
+
diagnosticReporthtml += `
|
|
2432
|
+
<tr>
|
|
2433
|
+
<td>${data["report"] ?? "-"}</td>
|
|
2434
|
+
<td>${data["performer"] ?? "-"}</td>
|
|
2435
|
+
<td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
|
|
2436
|
+
</tr>`;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
if (observationExists) {
|
|
2442
|
+
html += observationhtml;
|
|
2443
|
+
html += `
|
|
2444
|
+
</tbody>
|
|
2445
|
+
</table>
|
|
2446
|
+
</div>`;
|
|
2447
|
+
}
|
|
2448
|
+
if (diagnosticReportExists) {
|
|
2449
|
+
html += diagnosticReporthtml;
|
|
2450
|
+
html += `
|
|
2451
|
+
</tbody>
|
|
2452
|
+
</table>
|
|
2453
|
+
</div>`;
|
|
2454
|
+
}
|
|
2455
|
+
html += `
|
|
2456
|
+
</div>`;
|
|
2457
|
+
return html;
|
|
2458
|
+
}
|
|
2001
2459
|
/**
|
|
2002
2460
|
* Internal static implementation that actually generates the narrative
|
|
2003
2461
|
* @param resources - FHIR resources array containing Observation and DiagnosticReport resources
|
|
@@ -2139,6 +2597,57 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
2139
2597
|
});
|
|
2140
2598
|
return _HistoryOfProceduresTemplate.generateStaticNarrative(resources, timezone);
|
|
2141
2599
|
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Generate HTML narrative for Procedure resources using summary
|
|
2602
|
+
* @param resources - FHIR Composition resources
|
|
2603
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2604
|
+
* @returns HTML string for rendering
|
|
2605
|
+
*/
|
|
2606
|
+
generateSummaryNarrative(resources, timezone) {
|
|
2607
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2608
|
+
let html = `
|
|
2609
|
+
<div>
|
|
2610
|
+
<table>
|
|
2611
|
+
<thead>
|
|
2612
|
+
<tr>
|
|
2613
|
+
<th>Procedure</th>
|
|
2614
|
+
<th>Performer</th>
|
|
2615
|
+
<th>Date</th>
|
|
2616
|
+
</tr>
|
|
2617
|
+
</thead>
|
|
2618
|
+
<tbody>`;
|
|
2619
|
+
for (const resourceItem of resources) {
|
|
2620
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
2621
|
+
const data = {};
|
|
2622
|
+
for (const columnData of rowData.section ?? []) {
|
|
2623
|
+
switch (columnData.title) {
|
|
2624
|
+
case "Procedure Name":
|
|
2625
|
+
data["procedure"] = columnData.text?.div ?? "";
|
|
2626
|
+
break;
|
|
2627
|
+
case "Performer":
|
|
2628
|
+
data["performer"] = columnData.text?.div ?? "";
|
|
2629
|
+
break;
|
|
2630
|
+
case "Performed Date":
|
|
2631
|
+
data["date"] = columnData.text?.div ?? "";
|
|
2632
|
+
break;
|
|
2633
|
+
default:
|
|
2634
|
+
break;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
html += `
|
|
2638
|
+
<tr>
|
|
2639
|
+
<td>${data["procedure"] ?? "-"}</td>
|
|
2640
|
+
<td>${data["performer"] ?? "-"}</td>
|
|
2641
|
+
<td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
|
|
2642
|
+
</tr>`;
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
html += `
|
|
2646
|
+
</tbody>
|
|
2647
|
+
</table>
|
|
2648
|
+
</div>`;
|
|
2649
|
+
return html;
|
|
2650
|
+
}
|
|
2142
2651
|
/**
|
|
2143
2652
|
* Internal static implementation that actually generates the narrative
|
|
2144
2653
|
* @param resources - FHIR Procedure resources
|
|
@@ -2235,6 +2744,7 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
2235
2744
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2236
2745
|
* @returns HTML string for rendering
|
|
2237
2746
|
*/
|
|
2747
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2238
2748
|
generateNarrative(resources, timezone) {
|
|
2239
2749
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2240
2750
|
let html = ``;
|
|
@@ -2252,7 +2762,6 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
2252
2762
|
<th>Onset Date</th>
|
|
2253
2763
|
<th>Recorded Date</th>
|
|
2254
2764
|
<th>Resolved Date</th>
|
|
2255
|
-
<th>Notes</th>
|
|
2256
2765
|
</tr>
|
|
2257
2766
|
</thead>
|
|
2258
2767
|
<tbody>`;
|
|
@@ -2262,7 +2771,6 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
2262
2771
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
2263
2772
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
2264
2773
|
<td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
|
|
2265
|
-
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
|
|
2266
2774
|
</tr>`;
|
|
2267
2775
|
}
|
|
2268
2776
|
html += `</tbody>
|
|
@@ -2415,7 +2923,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2415
2923
|
<th>Problem</th>
|
|
2416
2924
|
<th>Onset Date</th>
|
|
2417
2925
|
<th>Recorded Date</th>
|
|
2418
|
-
<th>Notes</th>
|
|
2419
2926
|
</tr>
|
|
2420
2927
|
</thead>
|
|
2421
2928
|
<tbody>`;
|
|
@@ -2424,7 +2931,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2424
2931
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
2425
2932
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
2426
2933
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
2427
|
-
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone, { styled: true, warning: true })}</td>
|
|
2428
2934
|
</tr>`;
|
|
2429
2935
|
}
|
|
2430
2936
|
html += `</tbody>
|
|
@@ -2440,7 +2946,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2440
2946
|
<th>Description</th>
|
|
2441
2947
|
<th>Summary</th>
|
|
2442
2948
|
<th>Findings</th>
|
|
2443
|
-
<th>Notes</th>
|
|
2444
2949
|
</tr>
|
|
2445
2950
|
</thead>
|
|
2446
2951
|
<tbody>`;
|
|
@@ -2469,7 +2974,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2469
2974
|
}
|
|
2470
2975
|
findingsHtml += "</ul>";
|
|
2471
2976
|
}
|
|
2472
|
-
const notes = templateUtilities.renderNotes(impression.note, timezone);
|
|
2473
2977
|
html += `
|
|
2474
2978
|
<tr id="${templateUtilities.narrativeLinkId(impression)}">
|
|
2475
2979
|
<td>${formattedDate}</td>
|
|
@@ -2477,7 +2981,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2477
2981
|
<td>${impression.description || ""}</td>
|
|
2478
2982
|
<td>${impression.summary || ""}</td>
|
|
2479
2983
|
<td>${findingsHtml}</td>
|
|
2480
|
-
<td>${notes}</td>
|
|
2481
2984
|
</tr>`;
|
|
2482
2985
|
}
|
|
2483
2986
|
html += `</tbody>
|