@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.cjs
CHANGED
|
@@ -109,6 +109,14 @@ var BLOOD_PRESSURE_LOINC_CODES = {
|
|
|
109
109
|
DIASTOLIC: "8462-4"
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
+
// src/structures/ips_section_constants.ts
|
|
113
|
+
var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
114
|
+
"Systolic Blood Pressure": "valueRatio.numerator.value",
|
|
115
|
+
"Diastolic Blood Pressure": "valueRatio.denominator.value",
|
|
116
|
+
"Default": "valueString"
|
|
117
|
+
};
|
|
118
|
+
var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
|
|
119
|
+
|
|
112
120
|
// src/structures/ips_section_resource_map.ts
|
|
113
121
|
var IPSSectionResourceFilters = {
|
|
114
122
|
// Patient section: only Patient resource
|
|
@@ -116,7 +124,7 @@ var IPSSectionResourceFilters = {
|
|
|
116
124
|
// Only include allergies
|
|
117
125
|
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "AllergyIntolerance",
|
|
118
126
|
// includes MedicationRequest, MedicationStatement. Medication is needed for medication names
|
|
119
|
-
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement"
|
|
127
|
+
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement"].includes(resource.resourceType) && resource.status === "active" || resource.resourceType === "Medication",
|
|
120
128
|
// Only include active conditions
|
|
121
129
|
["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
|
|
122
130
|
// Only include completed immunizations
|
|
@@ -143,10 +151,13 @@ var IPSSectionResourceFilters = {
|
|
|
143
151
|
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
|
|
144
152
|
};
|
|
145
153
|
var IPSSectionSummaryCompositionFilter = {
|
|
146
|
-
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system ===
|
|
147
|
-
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system ===
|
|
148
|
-
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system ===
|
|
149
|
-
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system ===
|
|
154
|
+
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "allergy_summary_document"),
|
|
155
|
+
["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
|
|
156
|
+
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
|
|
157
|
+
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
|
|
158
|
+
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
|
|
159
|
+
["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)),
|
|
160
|
+
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
|
|
150
161
|
};
|
|
151
162
|
var IPSSectionResourceHelper = class {
|
|
152
163
|
static getResourceFilterForSection(section) {
|
|
@@ -850,9 +861,11 @@ var TemplateUtilities = class {
|
|
|
850
861
|
if (dateValue instanceof Date) {
|
|
851
862
|
dateTime = import_luxon.DateTime.fromJSDate(dateValue);
|
|
852
863
|
} else if (typeof dateValue === "string") {
|
|
853
|
-
dateTime = import_luxon.DateTime.fromISO(dateValue);
|
|
854
864
|
if (!dateValue.includes("T")) {
|
|
855
865
|
dateOnly = true;
|
|
866
|
+
dateTime = import_luxon.DateTime.fromISO(dateValue, { zone: "utc" });
|
|
867
|
+
} else {
|
|
868
|
+
dateTime = import_luxon.DateTime.fromISO(dateValue);
|
|
856
869
|
}
|
|
857
870
|
} else {
|
|
858
871
|
dateTime = import_luxon.DateTime.fromISO(String(dateValue));
|
|
@@ -954,6 +967,9 @@ var TemplateUtilities = class {
|
|
|
954
967
|
}
|
|
955
968
|
};
|
|
956
969
|
|
|
970
|
+
// src/constants.ts
|
|
971
|
+
var ADDRESS_SIMILARITY_THRESHOLD = 70;
|
|
972
|
+
|
|
957
973
|
// src/narratives/templates/typescript/PatientTemplate.ts
|
|
958
974
|
var PatientTemplate = class _PatientTemplate {
|
|
959
975
|
/**
|
|
@@ -980,7 +996,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
980
996
|
<li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
|
|
981
997
|
<li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
|
|
982
998
|
<li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
|
|
983
|
-
<li><strong>Identifier(s):</strong>${this.renderIdentifiers(combinedPatient)}</li>
|
|
984
999
|
<li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
|
|
985
1000
|
<li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
|
|
986
1001
|
<li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus?.text || ""}</li>
|
|
@@ -1001,7 +1016,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1001
1016
|
}
|
|
1002
1017
|
const combined = patients[0];
|
|
1003
1018
|
const allNames = [];
|
|
1004
|
-
const allIdentifiers = [];
|
|
1005
1019
|
const allTelecom = [];
|
|
1006
1020
|
const allAddresses = [];
|
|
1007
1021
|
const allCommunication = [];
|
|
@@ -1009,9 +1023,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1009
1023
|
if (patient.name) {
|
|
1010
1024
|
allNames.push(...patient.name);
|
|
1011
1025
|
}
|
|
1012
|
-
if (patient.identifier) {
|
|
1013
|
-
allIdentifiers.push(...patient.identifier);
|
|
1014
|
-
}
|
|
1015
1026
|
if (patient.telecom) {
|
|
1016
1027
|
allTelecom.push(...patient.telecom);
|
|
1017
1028
|
}
|
|
@@ -1038,7 +1049,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1038
1049
|
}
|
|
1039
1050
|
});
|
|
1040
1051
|
combined.name = allNames;
|
|
1041
|
-
combined.identifier = allIdentifiers;
|
|
1042
1052
|
combined.telecom = allTelecom;
|
|
1043
1053
|
combined.address = allAddresses;
|
|
1044
1054
|
combined.communication = allCommunication;
|
|
@@ -1064,21 +1074,6 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1064
1074
|
});
|
|
1065
1075
|
return Array.from(uniqueNames).map((nameText) => `<ul><li>${nameText}</li></ul>`).join("");
|
|
1066
1076
|
}
|
|
1067
|
-
/**
|
|
1068
|
-
* Renders patient identifiers as HTML list items
|
|
1069
|
-
* @param patient - Patient resources
|
|
1070
|
-
* @returns HTML string of list items
|
|
1071
|
-
*/
|
|
1072
|
-
static renderIdentifiers(patient) {
|
|
1073
|
-
if (!patient.identifier || patient.identifier.length === 0) {
|
|
1074
|
-
return "";
|
|
1075
|
-
}
|
|
1076
|
-
return patient.identifier.map((id) => {
|
|
1077
|
-
const system = id.system || "";
|
|
1078
|
-
const value = id.value || "";
|
|
1079
|
-
return `<ul><li>${system}: ${value}</li></ul>`;
|
|
1080
|
-
}).join("");
|
|
1081
|
-
}
|
|
1082
1077
|
/**
|
|
1083
1078
|
* Renders patient telecom information grouped by system
|
|
1084
1079
|
* @param patient - Patient resources
|
|
@@ -1180,7 +1175,89 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1180
1175
|
uniqueAddresses.add(addressText);
|
|
1181
1176
|
}
|
|
1182
1177
|
});
|
|
1183
|
-
|
|
1178
|
+
const deduplicatedAddresses = this.deduplicateSimilarAddresses(Array.from(uniqueAddresses));
|
|
1179
|
+
return deduplicatedAddresses.map((addressText) => `<ul><li>${addressText}</li></ul>`).join("");
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Calculates the similarity between two strings using Levenshtein distance
|
|
1183
|
+
* Returns a percentage (0-100) indicating how similar the strings are
|
|
1184
|
+
* @param str1 - First string
|
|
1185
|
+
* @param str2 - Second string
|
|
1186
|
+
* @returns Similarity percentage (0-100)
|
|
1187
|
+
*/
|
|
1188
|
+
static calculateStringSimilarity(str1, str2) {
|
|
1189
|
+
const longer = str1.length > str2.length ? str1 : str2;
|
|
1190
|
+
const shorter = str1.length > str2.length ? str2 : str1;
|
|
1191
|
+
if (longer.length === 0) {
|
|
1192
|
+
return 100;
|
|
1193
|
+
}
|
|
1194
|
+
const editDistance = this.levenshteinDistance(longer.toLowerCase(), shorter.toLowerCase());
|
|
1195
|
+
return (longer.length - editDistance) / longer.length * 100;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Calculates the Levenshtein distance between two strings
|
|
1199
|
+
* @param str1 - First string
|
|
1200
|
+
* @param str2 - Second string
|
|
1201
|
+
* @returns The Levenshtein distance
|
|
1202
|
+
*/
|
|
1203
|
+
static levenshteinDistance(str1, str2) {
|
|
1204
|
+
const matrix = [];
|
|
1205
|
+
for (let i = 0; i <= str2.length; i++) {
|
|
1206
|
+
matrix[i] = [i];
|
|
1207
|
+
}
|
|
1208
|
+
for (let j = 0; j <= str1.length; j++) {
|
|
1209
|
+
matrix[0][j] = j;
|
|
1210
|
+
}
|
|
1211
|
+
for (let i = 1; i <= str2.length; i++) {
|
|
1212
|
+
for (let j = 1; j <= str1.length; j++) {
|
|
1213
|
+
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
1214
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
1215
|
+
} else {
|
|
1216
|
+
matrix[i][j] = Math.min(
|
|
1217
|
+
matrix[i - 1][j - 1] + 1,
|
|
1218
|
+
// substitution
|
|
1219
|
+
matrix[i][j - 1] + 1,
|
|
1220
|
+
// insertion
|
|
1221
|
+
matrix[i - 1][j] + 1
|
|
1222
|
+
// deletion
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return matrix[str2.length][str1.length];
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Deduplicates addresses that are more than ADDRESS_SIMILARITY_THRESHOLD% similar
|
|
1231
|
+
* @param addresses - Array of address strings
|
|
1232
|
+
* @returns Array of deduplicated addresses
|
|
1233
|
+
*/
|
|
1234
|
+
static deduplicateSimilarAddresses(addresses) {
|
|
1235
|
+
if (addresses.length <= 1) {
|
|
1236
|
+
return addresses;
|
|
1237
|
+
}
|
|
1238
|
+
const deduplicated = [];
|
|
1239
|
+
const processed = /* @__PURE__ */ new Set();
|
|
1240
|
+
for (let i = 0; i < addresses.length; i++) {
|
|
1241
|
+
if (processed.has(i)) {
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
let keepAddress = addresses[i];
|
|
1245
|
+
processed.add(i);
|
|
1246
|
+
for (let j = i + 1; j < addresses.length; j++) {
|
|
1247
|
+
if (processed.has(j)) {
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
const similarity = this.calculateStringSimilarity(addresses[i], addresses[j]);
|
|
1251
|
+
if (similarity > ADDRESS_SIMILARITY_THRESHOLD) {
|
|
1252
|
+
processed.add(j);
|
|
1253
|
+
if (addresses[j].length > keepAddress.length) {
|
|
1254
|
+
keepAddress = addresses[j];
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
deduplicated.push(keepAddress);
|
|
1259
|
+
}
|
|
1260
|
+
return deduplicated;
|
|
1184
1261
|
}
|
|
1185
1262
|
/**
|
|
1186
1263
|
* Renders patient deceased status
|
|
@@ -1428,6 +1505,75 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1428
1505
|
generateNarrative(resources, timezone) {
|
|
1429
1506
|
return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
|
|
1430
1507
|
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Generate HTML narrative for Medication resources using summary
|
|
1510
|
+
* @param resources - FHIR Composition resources
|
|
1511
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1512
|
+
* @returns HTML string for rendering
|
|
1513
|
+
*/
|
|
1514
|
+
generateSummaryNarrative(resources, timezone) {
|
|
1515
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
1516
|
+
let html = `
|
|
1517
|
+
<div>
|
|
1518
|
+
<table>
|
|
1519
|
+
<thead>
|
|
1520
|
+
<tr>
|
|
1521
|
+
<th>Medication</th>
|
|
1522
|
+
<th>Sig</th>
|
|
1523
|
+
<th>Days of Supply</th>
|
|
1524
|
+
<th>Refills</th>
|
|
1525
|
+
<th>Start Date</th>
|
|
1526
|
+
</tr>
|
|
1527
|
+
</thead>
|
|
1528
|
+
<tbody>`;
|
|
1529
|
+
for (const resourceItem of resources) {
|
|
1530
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
1531
|
+
const data = {};
|
|
1532
|
+
for (const columnData of rowData.section ?? []) {
|
|
1533
|
+
switch (columnData.title) {
|
|
1534
|
+
case "Medication Name":
|
|
1535
|
+
data["medication"] = columnData.text?.div ?? "";
|
|
1536
|
+
break;
|
|
1537
|
+
case "Status":
|
|
1538
|
+
data["status"] = columnData.text?.div ?? "";
|
|
1539
|
+
break;
|
|
1540
|
+
case "Prescriber Instruction":
|
|
1541
|
+
data["sig-prescriber"] = columnData.text?.div ?? "";
|
|
1542
|
+
break;
|
|
1543
|
+
case "Pharmacy Instruction":
|
|
1544
|
+
data["sig-pharmacy"] = columnData.text?.div ?? "";
|
|
1545
|
+
break;
|
|
1546
|
+
case "Days Of Supply":
|
|
1547
|
+
data["daysOfSupply"] = columnData.text?.div ?? "";
|
|
1548
|
+
break;
|
|
1549
|
+
case "Refills Remaining":
|
|
1550
|
+
data["refills"] = columnData.text?.div ?? "";
|
|
1551
|
+
break;
|
|
1552
|
+
case "Authored On Date":
|
|
1553
|
+
data["startDate"] = columnData.text?.div ?? "";
|
|
1554
|
+
break;
|
|
1555
|
+
default:
|
|
1556
|
+
break;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
if (data["status"] === "active") {
|
|
1560
|
+
html += `
|
|
1561
|
+
<tr>
|
|
1562
|
+
<td>${data["medication"]}</td>
|
|
1563
|
+
<td>${data["sig-prescriber"] || data["sig-pharmacy"]}</td>
|
|
1564
|
+
<td>${data["daysOfSupply"]}</td>
|
|
1565
|
+
<td>${data["refills"]}</td>
|
|
1566
|
+
<td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
|
|
1567
|
+
</tr>`;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
html += `
|
|
1572
|
+
</tbody>
|
|
1573
|
+
</table>
|
|
1574
|
+
</div>`;
|
|
1575
|
+
return html;
|
|
1576
|
+
}
|
|
1431
1577
|
/**
|
|
1432
1578
|
* Safely parse a date string and return a valid Date object or null
|
|
1433
1579
|
* @param dateString - The date string to parse
|
|
@@ -1440,55 +1586,6 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1440
1586
|
const date = new Date(dateString);
|
|
1441
1587
|
return !isNaN(date.getTime()) ? date : null;
|
|
1442
1588
|
}
|
|
1443
|
-
/**
|
|
1444
|
-
* Determine if a MedicationRequest is active
|
|
1445
|
-
* @param medicationRequest - The MedicationRequest resource
|
|
1446
|
-
* @returns boolean indicating if the medication request is active
|
|
1447
|
-
*/
|
|
1448
|
-
static isActiveMedicationRequest(medicationRequest) {
|
|
1449
|
-
const status = medicationRequest.status?.toLowerCase();
|
|
1450
|
-
if (status === "active" || status === "unknown") {
|
|
1451
|
-
return true;
|
|
1452
|
-
}
|
|
1453
|
-
if (status === "completed" || status === "cancelled" || status === "stopped" || status === "draft") {
|
|
1454
|
-
return false;
|
|
1455
|
-
}
|
|
1456
|
-
const endDate = medicationRequest.dispenseRequest?.validityPeriod?.end;
|
|
1457
|
-
if (!endDate) {
|
|
1458
|
-
return true;
|
|
1459
|
-
}
|
|
1460
|
-
const parsedEndDate = this.parseDate(endDate);
|
|
1461
|
-
if (!parsedEndDate) {
|
|
1462
|
-
return true;
|
|
1463
|
-
}
|
|
1464
|
-
return parsedEndDate.getTime() > Date.now();
|
|
1465
|
-
}
|
|
1466
|
-
/**
|
|
1467
|
-
* Determine if a MedicationStatement is active
|
|
1468
|
-
* @param medicationStatement - The MedicationStatement resource
|
|
1469
|
-
* @returns boolean indicating if the medication statement is active
|
|
1470
|
-
*/
|
|
1471
|
-
static isActiveMedicationStatement(medicationStatement) {
|
|
1472
|
-
const status = medicationStatement.status?.toLowerCase();
|
|
1473
|
-
if (status === "active" || status === "intended" || status === "unknown") {
|
|
1474
|
-
return true;
|
|
1475
|
-
}
|
|
1476
|
-
if (status === "completed" || status === "stopped" || status === "not-taken") {
|
|
1477
|
-
return false;
|
|
1478
|
-
}
|
|
1479
|
-
let endDate;
|
|
1480
|
-
if (medicationStatement.effectivePeriod?.end) {
|
|
1481
|
-
endDate = medicationStatement.effectivePeriod.end;
|
|
1482
|
-
}
|
|
1483
|
-
if (!endDate) {
|
|
1484
|
-
return true;
|
|
1485
|
-
}
|
|
1486
|
-
const parsedEndDate = this.parseDate(endDate);
|
|
1487
|
-
if (!parsedEndDate) {
|
|
1488
|
-
return true;
|
|
1489
|
-
}
|
|
1490
|
-
return parsedEndDate.getTime() > Date.now();
|
|
1491
|
-
}
|
|
1492
1589
|
/**
|
|
1493
1590
|
* Internal static implementation that actually generates the narrative
|
|
1494
1591
|
* @param resources - FHIR Medication resources
|
|
@@ -1502,20 +1599,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1502
1599
|
const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
|
|
1503
1600
|
const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
|
|
1504
1601
|
const allActiveMedications = [];
|
|
1505
|
-
const allInactiveMedications = [];
|
|
1506
1602
|
medicationRequests.forEach((mr) => {
|
|
1507
|
-
|
|
1508
|
-
allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
|
|
1509
|
-
} else {
|
|
1510
|
-
allInactiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
|
|
1511
|
-
}
|
|
1603
|
+
allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
|
|
1512
1604
|
});
|
|
1513
1605
|
medicationStatements.forEach((ms) => {
|
|
1514
|
-
|
|
1515
|
-
allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
|
|
1516
|
-
} else {
|
|
1517
|
-
allInactiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
|
|
1518
|
-
}
|
|
1606
|
+
allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
|
|
1519
1607
|
});
|
|
1520
1608
|
const sortMedications = (medications) => {
|
|
1521
1609
|
medications.sort((a, b) => {
|
|
@@ -1545,11 +1633,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1545
1633
|
};
|
|
1546
1634
|
if (allActiveMedications.length > 0) {
|
|
1547
1635
|
sortMedications(allActiveMedications);
|
|
1548
|
-
html += this.renderCombinedMedications(templateUtilities, allActiveMedications
|
|
1549
|
-
}
|
|
1550
|
-
if (allInactiveMedications.length > 0) {
|
|
1551
|
-
sortMedications(allInactiveMedications);
|
|
1552
|
-
html += this.renderCombinedMedications(templateUtilities, allInactiveMedications, false);
|
|
1636
|
+
html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
|
|
1553
1637
|
}
|
|
1554
1638
|
return html;
|
|
1555
1639
|
}
|
|
@@ -1587,12 +1671,10 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1587
1671
|
* Render HTML table for combined MedicationRequest and MedicationStatement resources
|
|
1588
1672
|
* @param templateUtilities - Instance of TemplateUtilities for utility functions
|
|
1589
1673
|
* @param medications - Array of combined medication resources
|
|
1590
|
-
* @param sectionTitle - Title for the section
|
|
1591
1674
|
* @returns HTML string for rendering
|
|
1592
1675
|
*/
|
|
1593
|
-
static renderCombinedMedications(templateUtilities, medications
|
|
1676
|
+
static renderCombinedMedications(templateUtilities, medications) {
|
|
1594
1677
|
let html = `
|
|
1595
|
-
<h3>${isActiveSection ? "Active Medications" : "Inactive Medications"}</h3>
|
|
1596
1678
|
<table>
|
|
1597
1679
|
<thead>
|
|
1598
1680
|
<tr>
|
|
@@ -1601,9 +1683,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1601
1683
|
<th>Sig</th>
|
|
1602
1684
|
<th>Dispense Quantity</th>
|
|
1603
1685
|
<th>Refills</th>
|
|
1604
|
-
<th>Start Date</th
|
|
1605
|
-
<th>End Date</th>`}
|
|
1606
|
-
<th>Status</th>
|
|
1686
|
+
<th>Start Date</th>
|
|
1607
1687
|
</tr>
|
|
1608
1688
|
</thead>
|
|
1609
1689
|
<tbody>`;
|
|
@@ -1615,12 +1695,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1615
1695
|
let dispenseQuantity = "-";
|
|
1616
1696
|
let refills = "-";
|
|
1617
1697
|
let startDate = "-";
|
|
1618
|
-
let endDate = "-";
|
|
1619
|
-
let status;
|
|
1620
1698
|
if (medication.type === "request") {
|
|
1621
1699
|
const mr = medication.resource;
|
|
1622
1700
|
type = "Request";
|
|
1623
|
-
status = mr.status ? String(mr.status) : "-";
|
|
1624
1701
|
medicationName = templateUtilities.getMedicationName(
|
|
1625
1702
|
mr.medicationReference || mr.medicationCodeableConcept
|
|
1626
1703
|
);
|
|
@@ -1634,14 +1711,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1634
1711
|
refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "-";
|
|
1635
1712
|
if (mr.dispenseRequest?.validityPeriod) {
|
|
1636
1713
|
startDate = mr.dispenseRequest.validityPeriod.start || "-";
|
|
1637
|
-
endDate = mr.dispenseRequest.validityPeriod.end || "-";
|
|
1638
1714
|
} else {
|
|
1639
1715
|
startDate = mr.authoredOn || "-";
|
|
1640
1716
|
}
|
|
1641
1717
|
} else {
|
|
1642
1718
|
const ms = medication.resource;
|
|
1643
1719
|
type = "Statement";
|
|
1644
|
-
status = ms.status ? String(ms.status) : "-";
|
|
1645
1720
|
medicationName = templateUtilities.getMedicationName(
|
|
1646
1721
|
ms.medicationReference || ms.medicationCodeableConcept
|
|
1647
1722
|
);
|
|
@@ -1650,7 +1725,6 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1650
1725
|
startDate = ms.effectiveDateTime;
|
|
1651
1726
|
} else if (ms.effectivePeriod) {
|
|
1652
1727
|
startDate = ms.effectivePeriod.start || "-";
|
|
1653
|
-
endDate = ms.effectivePeriod.end || "-";
|
|
1654
1728
|
}
|
|
1655
1729
|
}
|
|
1656
1730
|
html += `
|
|
@@ -1660,9 +1734,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1660
1734
|
<td>${sig}</td>
|
|
1661
1735
|
<td>${dispenseQuantity}</td>
|
|
1662
1736
|
<td>${refills}</td>
|
|
1663
|
-
<td>${startDate}</td
|
|
1664
|
-
<td>${endDate}</td>`}
|
|
1665
|
-
<td>${status}</td>
|
|
1737
|
+
<td>${startDate}</td>
|
|
1666
1738
|
</tr>`;
|
|
1667
1739
|
}
|
|
1668
1740
|
html += `
|
|
@@ -1803,6 +1875,7 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1803
1875
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1804
1876
|
* @returns HTML string for rendering
|
|
1805
1877
|
*/
|
|
1878
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1806
1879
|
static generateStaticNarrative(resources, timezone) {
|
|
1807
1880
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1808
1881
|
let html = ``;
|
|
@@ -1819,7 +1892,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1819
1892
|
<th>Problem</th>
|
|
1820
1893
|
<th>Onset Date</th>
|
|
1821
1894
|
<th>Recorded Date</th>
|
|
1822
|
-
<th>Notes</th>
|
|
1823
1895
|
</tr>
|
|
1824
1896
|
</thead>
|
|
1825
1897
|
<tbody>`;
|
|
@@ -1828,7 +1900,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1828
1900
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
1829
1901
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
1830
1902
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
1831
|
-
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
|
|
1832
1903
|
</tr>`;
|
|
1833
1904
|
}
|
|
1834
1905
|
html += `</tbody>
|
|
@@ -1837,13 +1908,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1837
1908
|
}
|
|
1838
1909
|
};
|
|
1839
1910
|
|
|
1840
|
-
// src/structures/ips_section_constants.ts
|
|
1841
|
-
var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
1842
|
-
"Systolic Blood Pressure": "valueRatio.numerator.value",
|
|
1843
|
-
"Diastolic Blood Pressure": "valueRatio.denominator.value",
|
|
1844
|
-
"Default": "valueString"
|
|
1845
|
-
};
|
|
1846
|
-
|
|
1847
1911
|
// src/narratives/templates/typescript/VitalSignsTemplate.ts
|
|
1848
1912
|
var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
1849
1913
|
/**
|
|
@@ -2026,6 +2090,400 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2026
2090
|
generateNarrative(resources, timezone) {
|
|
2027
2091
|
return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
|
|
2028
2092
|
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Helper function to format observation data fields
|
|
2095
|
+
* @param obsData - Record containing observation data fields
|
|
2096
|
+
*/
|
|
2097
|
+
formatSummaryObservationData(obsData) {
|
|
2098
|
+
const valueType = obsData["valueType"];
|
|
2099
|
+
switch (valueType) {
|
|
2100
|
+
case "valueQuantity":
|
|
2101
|
+
if (obsData["value"] && obsData["unit"]) {
|
|
2102
|
+
obsData["formattedValue"] = `${obsData["value"]} ${obsData["unit"]}`;
|
|
2103
|
+
} else if (obsData["value"]) {
|
|
2104
|
+
obsData["formattedValue"] = obsData["value"];
|
|
2105
|
+
}
|
|
2106
|
+
break;
|
|
2107
|
+
case "valueCodeableConcept":
|
|
2108
|
+
case "valueString":
|
|
2109
|
+
case "valueBoolean":
|
|
2110
|
+
case "valueInteger":
|
|
2111
|
+
case "valueDateTime":
|
|
2112
|
+
case "valueTime":
|
|
2113
|
+
obsData["formattedValue"] = obsData["value"] ?? "";
|
|
2114
|
+
break;
|
|
2115
|
+
case "valuePeriod":
|
|
2116
|
+
if (obsData["valuePeriodStart"] && obsData["valuePeriodEnd"]) {
|
|
2117
|
+
obsData["formattedValue"] = `${obsData["valuePeriodStart"]} - ${obsData["valuePeriodEnd"]}`;
|
|
2118
|
+
} else if (obsData["valuePeriodStart"]) {
|
|
2119
|
+
obsData["formattedValue"] = `From ${obsData["valuePeriodStart"]}`;
|
|
2120
|
+
} else if (obsData["valuePeriodEnd"]) {
|
|
2121
|
+
obsData["formattedValue"] = `Until ${obsData["valuePeriodEnd"]}`;
|
|
2122
|
+
}
|
|
2123
|
+
break;
|
|
2124
|
+
case "valueSampledData": {
|
|
2125
|
+
const sampledParts = [];
|
|
2126
|
+
if (obsData["sampledDataOriginValue"]) {
|
|
2127
|
+
sampledParts.push(`Origin: ${obsData["sampledDataOriginValue"]}${obsData["sampledDataOriginUnit"] ? " " + obsData["sampledDataOriginUnit"] : ""}`);
|
|
2128
|
+
}
|
|
2129
|
+
if (obsData["sampledDataPeriod"]) {
|
|
2130
|
+
sampledParts.push(`Period: ${obsData["sampledDataPeriod"]}`);
|
|
2131
|
+
}
|
|
2132
|
+
if (obsData["sampledDataFactor"]) {
|
|
2133
|
+
sampledParts.push(`Factor: ${obsData["sampledDataFactor"]}`);
|
|
2134
|
+
}
|
|
2135
|
+
if (obsData["sampledDataLowerLimit"]) {
|
|
2136
|
+
sampledParts.push(`Lower: ${obsData["sampledDataLowerLimit"]}`);
|
|
2137
|
+
}
|
|
2138
|
+
if (obsData["sampledDataUpperLimit"]) {
|
|
2139
|
+
sampledParts.push(`Upper: ${obsData["sampledDataUpperLimit"]}`);
|
|
2140
|
+
}
|
|
2141
|
+
if (obsData["sampledDataData"]) {
|
|
2142
|
+
sampledParts.push(`Data: ${obsData["sampledDataData"]}`);
|
|
2143
|
+
}
|
|
2144
|
+
obsData["formattedValue"] = sampledParts.join(", ");
|
|
2145
|
+
break;
|
|
2146
|
+
}
|
|
2147
|
+
case "valueRange": {
|
|
2148
|
+
const rangeParts = [];
|
|
2149
|
+
if (obsData["valueRangeLowValue"]) {
|
|
2150
|
+
rangeParts.push(`${obsData["valueRangeLowValue"]}${obsData["valueRangeLowUnit"] ? " " + obsData["valueRangeLowUnit"] : ""}`);
|
|
2151
|
+
}
|
|
2152
|
+
if (obsData["valueRangeHighValue"]) {
|
|
2153
|
+
rangeParts.push(`${obsData["valueRangeHighValue"]}${obsData["valueRangeHighUnit"] ? " " + obsData["valueRangeHighUnit"] : ""}`);
|
|
2154
|
+
}
|
|
2155
|
+
obsData["formattedValue"] = rangeParts.join(" - ");
|
|
2156
|
+
break;
|
|
2157
|
+
}
|
|
2158
|
+
case "valueRatio": {
|
|
2159
|
+
const numerator = obsData["valueRatioNumeratorValue"] ? `${obsData["valueRatioNumeratorValue"]}${obsData["valueRatioNumeratorUnit"] ? " " + obsData["valueRatioNumeratorUnit"] : ""}` : "";
|
|
2160
|
+
const denominator = obsData["valueRatioDenominatorValue"] ? `${obsData["valueRatioDenominatorValue"]}${obsData["valueRatioDenominatorUnit"] ? " " + obsData["valueRatioDenominatorUnit"] : ""}` : "";
|
|
2161
|
+
if (numerator && denominator) {
|
|
2162
|
+
obsData["formattedValue"] = `${numerator} / ${denominator}`;
|
|
2163
|
+
} else if (numerator) {
|
|
2164
|
+
obsData["formattedValue"] = numerator;
|
|
2165
|
+
}
|
|
2166
|
+
break;
|
|
2167
|
+
}
|
|
2168
|
+
default:
|
|
2169
|
+
obsData["formattedValue"] = obsData["value"] ?? "";
|
|
2170
|
+
break;
|
|
2171
|
+
}
|
|
2172
|
+
if (obsData["referenceRangeLow"]) {
|
|
2173
|
+
obsData["referenceRange"] = obsData["referenceRangeLow"] + " " + obsData["referenceRangeLowUnit"];
|
|
2174
|
+
}
|
|
2175
|
+
if (obsData["referenceRangeHigh"]) {
|
|
2176
|
+
if (obsData["referenceRange"]) {
|
|
2177
|
+
obsData["referenceRange"] += " - ";
|
|
2178
|
+
} else {
|
|
2179
|
+
obsData["referenceRange"] = "";
|
|
2180
|
+
}
|
|
2181
|
+
obsData["referenceRange"] += obsData["referenceRangeHigh"] + " " + obsData["referenceRangeHighUnit"];
|
|
2182
|
+
}
|
|
2183
|
+
if (obsData["referenceRangeAgeLowValue"] || obsData["referenceRangeAgeHighValue"]) {
|
|
2184
|
+
const ageParts = [];
|
|
2185
|
+
if (obsData["referenceRangeAgeLowValue"]) {
|
|
2186
|
+
ageParts.push(`${obsData["referenceRangeAgeLowValue"]}${obsData["referenceRangeAgeLowUnit"] ? " " + obsData["referenceRangeAgeLowUnit"] : ""}`);
|
|
2187
|
+
}
|
|
2188
|
+
if (obsData["referenceRangeAgeHighValue"]) {
|
|
2189
|
+
ageParts.push(`${obsData["referenceRangeAgeHighValue"]}${obsData["referenceRangeAgeHighUnit"] ? " " + obsData["referenceRangeAgeHighUnit"] : ""}`);
|
|
2190
|
+
}
|
|
2191
|
+
if (obsData["referenceRange"]) {
|
|
2192
|
+
obsData["referenceRange"] += ` (Age: ${ageParts.join(" - ")})`;
|
|
2193
|
+
} else {
|
|
2194
|
+
obsData["referenceRange"] = `Age: ${ageParts.join(" - ")}`;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Helper function to extract observation field data
|
|
2200
|
+
* @param column - Column data from the summary
|
|
2201
|
+
* @param targetData - Record to populate with extracted data
|
|
2202
|
+
*/
|
|
2203
|
+
extractSummaryObservationFields(column, targetData) {
|
|
2204
|
+
switch (column.title) {
|
|
2205
|
+
case "Labs Name":
|
|
2206
|
+
targetData["code"] = column.text?.div ?? "";
|
|
2207
|
+
break;
|
|
2208
|
+
case "effectiveDateTime":
|
|
2209
|
+
targetData["effectiveDateTime"] = column.text?.div ?? "";
|
|
2210
|
+
break;
|
|
2211
|
+
case "effectivePeriod.start":
|
|
2212
|
+
targetData["effectivePeriodStart"] = column.text?.div ?? "";
|
|
2213
|
+
break;
|
|
2214
|
+
case "effectivePeriod.end":
|
|
2215
|
+
targetData["effectivePeriodEnd"] = column.text?.div ?? "";
|
|
2216
|
+
break;
|
|
2217
|
+
// valueQuantity
|
|
2218
|
+
case "valueQuantity.value":
|
|
2219
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2220
|
+
targetData["valueType"] = "valueQuantity";
|
|
2221
|
+
break;
|
|
2222
|
+
case "valueQuantity.unit":
|
|
2223
|
+
targetData["unit"] = column.text?.div ?? "";
|
|
2224
|
+
break;
|
|
2225
|
+
// valueCodeableConcept
|
|
2226
|
+
case "valueCodeableConcept.text":
|
|
2227
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2228
|
+
targetData["valueType"] = "valueCodeableConcept";
|
|
2229
|
+
break;
|
|
2230
|
+
case "valueCodeableConcept.coding.display":
|
|
2231
|
+
if (!targetData["value"]) {
|
|
2232
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2233
|
+
targetData["valueType"] = "valueCodeableConcept";
|
|
2234
|
+
}
|
|
2235
|
+
break;
|
|
2236
|
+
// valueString
|
|
2237
|
+
case "valueString":
|
|
2238
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2239
|
+
targetData["valueType"] = "valueString";
|
|
2240
|
+
break;
|
|
2241
|
+
// valueBoolean
|
|
2242
|
+
case "valueBoolean":
|
|
2243
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2244
|
+
targetData["valueType"] = "valueBoolean";
|
|
2245
|
+
break;
|
|
2246
|
+
// valueInteger
|
|
2247
|
+
case "valueInteger":
|
|
2248
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2249
|
+
targetData["valueType"] = "valueInteger";
|
|
2250
|
+
break;
|
|
2251
|
+
// valueDateTime
|
|
2252
|
+
case "valueDateTime":
|
|
2253
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2254
|
+
targetData["valueType"] = "valueDateTime";
|
|
2255
|
+
break;
|
|
2256
|
+
// valuePeriod
|
|
2257
|
+
case "valuePeriod.start":
|
|
2258
|
+
targetData["valuePeriodStart"] = column.text?.div ?? "";
|
|
2259
|
+
targetData["valueType"] = "valuePeriod";
|
|
2260
|
+
break;
|
|
2261
|
+
case "valuePeriod.end":
|
|
2262
|
+
targetData["valuePeriodEnd"] = column.text?.div ?? "";
|
|
2263
|
+
targetData["valueType"] = "valuePeriod";
|
|
2264
|
+
break;
|
|
2265
|
+
// valueTime
|
|
2266
|
+
case "valueTime":
|
|
2267
|
+
targetData["value"] = column.text?.div ?? "";
|
|
2268
|
+
targetData["valueType"] = "valueTime";
|
|
2269
|
+
break;
|
|
2270
|
+
// valueSampledData
|
|
2271
|
+
case "valueSampledData.origin.value":
|
|
2272
|
+
targetData["sampledDataOriginValue"] = column.text?.div ?? "";
|
|
2273
|
+
targetData["valueType"] = "valueSampledData";
|
|
2274
|
+
break;
|
|
2275
|
+
case "valueSampledData.origin.unit":
|
|
2276
|
+
targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
|
|
2277
|
+
break;
|
|
2278
|
+
case "valueSampledData.period":
|
|
2279
|
+
targetData["sampledDataPeriod"] = column.text?.div ?? "";
|
|
2280
|
+
break;
|
|
2281
|
+
case "valueSampledData.factor":
|
|
2282
|
+
targetData["sampledDataFactor"] = column.text?.div ?? "";
|
|
2283
|
+
break;
|
|
2284
|
+
case "valueSampledData.lowerLimit":
|
|
2285
|
+
targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
|
|
2286
|
+
break;
|
|
2287
|
+
case "valueSampledData.upperLimit":
|
|
2288
|
+
targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
|
|
2289
|
+
break;
|
|
2290
|
+
case "valueSampledData.data":
|
|
2291
|
+
targetData["sampledDataData"] = column.text?.div ?? "";
|
|
2292
|
+
break;
|
|
2293
|
+
// valueRange
|
|
2294
|
+
case "valueRange.low.value":
|
|
2295
|
+
targetData["valueRangeLowValue"] = column.text?.div ?? "";
|
|
2296
|
+
targetData["valueType"] = "valueRange";
|
|
2297
|
+
break;
|
|
2298
|
+
case "valueRange.low.unit":
|
|
2299
|
+
targetData["valueRangeLowUnit"] = column.text?.div ?? "";
|
|
2300
|
+
break;
|
|
2301
|
+
case "valueRange.high.value":
|
|
2302
|
+
targetData["valueRangeHighValue"] = column.text?.div ?? "";
|
|
2303
|
+
break;
|
|
2304
|
+
case "valueRange.high.unit":
|
|
2305
|
+
targetData["valueRangeHighUnit"] = column.text?.div ?? "";
|
|
2306
|
+
break;
|
|
2307
|
+
// valueRatio
|
|
2308
|
+
case "valueRatio.numerator.value":
|
|
2309
|
+
targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
|
|
2310
|
+
targetData["valueType"] = "valueRatio";
|
|
2311
|
+
break;
|
|
2312
|
+
case "valueRatio.numerator.unit":
|
|
2313
|
+
targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
|
|
2314
|
+
break;
|
|
2315
|
+
case "valueRatio.denominator.value":
|
|
2316
|
+
targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
|
|
2317
|
+
break;
|
|
2318
|
+
case "valueRatio.denominator.unit":
|
|
2319
|
+
targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
|
|
2320
|
+
break;
|
|
2321
|
+
// referenceRange
|
|
2322
|
+
case "referenceRange.low.value":
|
|
2323
|
+
targetData["referenceRangeLow"] = column.text?.div ?? "";
|
|
2324
|
+
break;
|
|
2325
|
+
case "referenceRange.low.unit":
|
|
2326
|
+
targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
|
|
2327
|
+
break;
|
|
2328
|
+
case "referenceRange.high.value":
|
|
2329
|
+
targetData["referenceRangeHigh"] = column.text?.div ?? "";
|
|
2330
|
+
break;
|
|
2331
|
+
case "referenceRange.high.unit":
|
|
2332
|
+
targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
|
|
2333
|
+
break;
|
|
2334
|
+
case "referenceRange.age.low.value":
|
|
2335
|
+
targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
|
|
2336
|
+
break;
|
|
2337
|
+
case "referenceRange.age.low.unit":
|
|
2338
|
+
targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
|
|
2339
|
+
break;
|
|
2340
|
+
case "referenceRange.age.high.value":
|
|
2341
|
+
targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
|
|
2342
|
+
break;
|
|
2343
|
+
case "referenceRange.age.high.unit":
|
|
2344
|
+
targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
|
|
2345
|
+
break;
|
|
2346
|
+
default:
|
|
2347
|
+
break;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Generate HTML narrative for Diagnostic Results & Observation resources using summary
|
|
2352
|
+
* @param resources - FHIR Composition resources
|
|
2353
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2354
|
+
* @returns HTML string for rendering
|
|
2355
|
+
*/
|
|
2356
|
+
generateSummaryNarrative(resources, timezone) {
|
|
2357
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2358
|
+
let html = `
|
|
2359
|
+
<div>`;
|
|
2360
|
+
let observationhtml = `
|
|
2361
|
+
<div>
|
|
2362
|
+
<h3>Observations</h3>
|
|
2363
|
+
<table>
|
|
2364
|
+
<thead>
|
|
2365
|
+
<tr>
|
|
2366
|
+
<th>Code</th>
|
|
2367
|
+
<th>Result</th>
|
|
2368
|
+
<th>Reference Range</th>
|
|
2369
|
+
<th>Date</th>
|
|
2370
|
+
</tr>
|
|
2371
|
+
</thead>
|
|
2372
|
+
<tbody>`;
|
|
2373
|
+
let diagnosticReporthtml = `
|
|
2374
|
+
<div>
|
|
2375
|
+
<h3>Diagnostic Reports</h3>
|
|
2376
|
+
<table>
|
|
2377
|
+
<thead>
|
|
2378
|
+
<tr>
|
|
2379
|
+
<th>Report</th>
|
|
2380
|
+
<th>Performer</th>
|
|
2381
|
+
<th>Issued</th>
|
|
2382
|
+
</tr>
|
|
2383
|
+
</thead>
|
|
2384
|
+
<tbody>`;
|
|
2385
|
+
let observationExists = false;
|
|
2386
|
+
let diagnosticReportExists = false;
|
|
2387
|
+
for (const resourceItem of resources) {
|
|
2388
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
2389
|
+
const data = {};
|
|
2390
|
+
const components = [];
|
|
2391
|
+
for (const columnData of rowData.section ?? []) {
|
|
2392
|
+
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
2393
|
+
if (columnData.text?.div === "Observation.component" && columnData.section) {
|
|
2394
|
+
for (const componentSection of columnData.section) {
|
|
2395
|
+
const componentData = {};
|
|
2396
|
+
for (const nestedColumn of componentSection.section ?? []) {
|
|
2397
|
+
this.extractSummaryObservationFields(nestedColumn, componentData);
|
|
2398
|
+
}
|
|
2399
|
+
if (Object.keys(componentData).length > 0) {
|
|
2400
|
+
components.push(componentData);
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
} else {
|
|
2404
|
+
this.extractSummaryObservationFields(columnData, data);
|
|
2405
|
+
}
|
|
2406
|
+
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2407
|
+
switch (columnData.title) {
|
|
2408
|
+
case "Diagnostic Report Name":
|
|
2409
|
+
data["report"] = columnData.text?.div ?? "";
|
|
2410
|
+
break;
|
|
2411
|
+
case "Performer":
|
|
2412
|
+
data["performer"] = columnData.text?.div ?? "";
|
|
2413
|
+
break;
|
|
2414
|
+
case "Issued Date":
|
|
2415
|
+
data["issued"] = columnData.text?.div ?? "";
|
|
2416
|
+
break;
|
|
2417
|
+
case "Status":
|
|
2418
|
+
data["status"] = columnData.text?.div ?? "";
|
|
2419
|
+
break;
|
|
2420
|
+
default:
|
|
2421
|
+
break;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
|
|
2426
|
+
observationExists = true;
|
|
2427
|
+
let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
|
|
2428
|
+
if (!date && data["effectivePeriodStart"]) {
|
|
2429
|
+
date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
|
|
2430
|
+
if (data["effectivePeriodEnd"]) {
|
|
2431
|
+
date += " - " + templateUtilities.renderTime(data["effectivePeriodEnd"], timezone);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
if (components.length > 0) {
|
|
2435
|
+
const groupName = data["code"] ?? "";
|
|
2436
|
+
for (const component of components) {
|
|
2437
|
+
this.formatSummaryObservationData(component);
|
|
2438
|
+
observationhtml += `
|
|
2439
|
+
<tr>
|
|
2440
|
+
<td>${groupName ? groupName + " - " : ""}${component["code"] ?? "-"}</td>
|
|
2441
|
+
<td>${component["formattedValue"] ?? "-"}</td>
|
|
2442
|
+
<td>${component["referenceRange"]?.trim() ?? "-"}</td>
|
|
2443
|
+
<td>${date ?? "-"}</td>
|
|
2444
|
+
</tr>`;
|
|
2445
|
+
}
|
|
2446
|
+
} else {
|
|
2447
|
+
this.formatSummaryObservationData(data);
|
|
2448
|
+
observationhtml += `
|
|
2449
|
+
<tr>
|
|
2450
|
+
<td>${data["code"] ?? "-"}</td>
|
|
2451
|
+
<td>${data["formattedValue"] ?? "-"}</td>
|
|
2452
|
+
<td>${data["referenceRange"]?.trim() ?? "-"}</td>
|
|
2453
|
+
<td>${date ?? "-"}</td>
|
|
2454
|
+
</tr>`;
|
|
2455
|
+
}
|
|
2456
|
+
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2457
|
+
if (data["status"] === "final") {
|
|
2458
|
+
diagnosticReportExists = true;
|
|
2459
|
+
diagnosticReporthtml += `
|
|
2460
|
+
<tr>
|
|
2461
|
+
<td>${data["report"] ?? "-"}</td>
|
|
2462
|
+
<td>${data["performer"] ?? "-"}</td>
|
|
2463
|
+
<td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
|
|
2464
|
+
</tr>`;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
if (observationExists) {
|
|
2470
|
+
html += observationhtml;
|
|
2471
|
+
html += `
|
|
2472
|
+
</tbody>
|
|
2473
|
+
</table>
|
|
2474
|
+
</div>`;
|
|
2475
|
+
}
|
|
2476
|
+
if (diagnosticReportExists) {
|
|
2477
|
+
html += diagnosticReporthtml;
|
|
2478
|
+
html += `
|
|
2479
|
+
</tbody>
|
|
2480
|
+
</table>
|
|
2481
|
+
</div>`;
|
|
2482
|
+
}
|
|
2483
|
+
html += `
|
|
2484
|
+
</div>`;
|
|
2485
|
+
return html;
|
|
2486
|
+
}
|
|
2029
2487
|
/**
|
|
2030
2488
|
* Internal static implementation that actually generates the narrative
|
|
2031
2489
|
* @param resources - FHIR resources array containing Observation and DiagnosticReport resources
|
|
@@ -2167,6 +2625,57 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
2167
2625
|
});
|
|
2168
2626
|
return _HistoryOfProceduresTemplate.generateStaticNarrative(resources, timezone);
|
|
2169
2627
|
}
|
|
2628
|
+
/**
|
|
2629
|
+
* Generate HTML narrative for Procedure resources using summary
|
|
2630
|
+
* @param resources - FHIR Composition resources
|
|
2631
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2632
|
+
* @returns HTML string for rendering
|
|
2633
|
+
*/
|
|
2634
|
+
generateSummaryNarrative(resources, timezone) {
|
|
2635
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2636
|
+
let html = `
|
|
2637
|
+
<div>
|
|
2638
|
+
<table>
|
|
2639
|
+
<thead>
|
|
2640
|
+
<tr>
|
|
2641
|
+
<th>Procedure</th>
|
|
2642
|
+
<th>Performer</th>
|
|
2643
|
+
<th>Date</th>
|
|
2644
|
+
</tr>
|
|
2645
|
+
</thead>
|
|
2646
|
+
<tbody>`;
|
|
2647
|
+
for (const resourceItem of resources) {
|
|
2648
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
2649
|
+
const data = {};
|
|
2650
|
+
for (const columnData of rowData.section ?? []) {
|
|
2651
|
+
switch (columnData.title) {
|
|
2652
|
+
case "Procedure Name":
|
|
2653
|
+
data["procedure"] = columnData.text?.div ?? "";
|
|
2654
|
+
break;
|
|
2655
|
+
case "Performer":
|
|
2656
|
+
data["performer"] = columnData.text?.div ?? "";
|
|
2657
|
+
break;
|
|
2658
|
+
case "Performed Date":
|
|
2659
|
+
data["date"] = columnData.text?.div ?? "";
|
|
2660
|
+
break;
|
|
2661
|
+
default:
|
|
2662
|
+
break;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
html += `
|
|
2666
|
+
<tr>
|
|
2667
|
+
<td>${data["procedure"] ?? "-"}</td>
|
|
2668
|
+
<td>${data["performer"] ?? "-"}</td>
|
|
2669
|
+
<td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
|
|
2670
|
+
</tr>`;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
html += `
|
|
2674
|
+
</tbody>
|
|
2675
|
+
</table>
|
|
2676
|
+
</div>`;
|
|
2677
|
+
return html;
|
|
2678
|
+
}
|
|
2170
2679
|
/**
|
|
2171
2680
|
* Internal static implementation that actually generates the narrative
|
|
2172
2681
|
* @param resources - FHIR Procedure resources
|
|
@@ -2263,6 +2772,7 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
2263
2772
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2264
2773
|
* @returns HTML string for rendering
|
|
2265
2774
|
*/
|
|
2775
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2266
2776
|
generateNarrative(resources, timezone) {
|
|
2267
2777
|
const templateUtilities = new TemplateUtilities(resources);
|
|
2268
2778
|
let html = ``;
|
|
@@ -2280,7 +2790,6 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
2280
2790
|
<th>Onset Date</th>
|
|
2281
2791
|
<th>Recorded Date</th>
|
|
2282
2792
|
<th>Resolved Date</th>
|
|
2283
|
-
<th>Notes</th>
|
|
2284
2793
|
</tr>
|
|
2285
2794
|
</thead>
|
|
2286
2795
|
<tbody>`;
|
|
@@ -2290,7 +2799,6 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
2290
2799
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
2291
2800
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
2292
2801
|
<td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
|
|
2293
|
-
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
|
|
2294
2802
|
</tr>`;
|
|
2295
2803
|
}
|
|
2296
2804
|
html += `</tbody>
|
|
@@ -2443,7 +2951,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2443
2951
|
<th>Problem</th>
|
|
2444
2952
|
<th>Onset Date</th>
|
|
2445
2953
|
<th>Recorded Date</th>
|
|
2446
|
-
<th>Notes</th>
|
|
2447
2954
|
</tr>
|
|
2448
2955
|
</thead>
|
|
2449
2956
|
<tbody>`;
|
|
@@ -2452,7 +2959,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2452
2959
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
2453
2960
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
2454
2961
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
2455
|
-
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone, { styled: true, warning: true })}</td>
|
|
2456
2962
|
</tr>`;
|
|
2457
2963
|
}
|
|
2458
2964
|
html += `</tbody>
|
|
@@ -2468,7 +2974,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2468
2974
|
<th>Description</th>
|
|
2469
2975
|
<th>Summary</th>
|
|
2470
2976
|
<th>Findings</th>
|
|
2471
|
-
<th>Notes</th>
|
|
2472
2977
|
</tr>
|
|
2473
2978
|
</thead>
|
|
2474
2979
|
<tbody>`;
|
|
@@ -2497,7 +3002,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2497
3002
|
}
|
|
2498
3003
|
findingsHtml += "</ul>";
|
|
2499
3004
|
}
|
|
2500
|
-
const notes = templateUtilities.renderNotes(impression.note, timezone);
|
|
2501
3005
|
html += `
|
|
2502
3006
|
<tr id="${templateUtilities.narrativeLinkId(impression)}">
|
|
2503
3007
|
<td>${formattedDate}</td>
|
|
@@ -2505,7 +3009,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2505
3009
|
<td>${impression.description || ""}</td>
|
|
2506
3010
|
<td>${impression.summary || ""}</td>
|
|
2507
3011
|
<td>${findingsHtml}</td>
|
|
2508
|
-
<td>${notes}</td>
|
|
2509
3012
|
</tr>`;
|
|
2510
3013
|
}
|
|
2511
3014
|
html += `</tbody>
|