@imranq2/fhirpatientsummary 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.cjs +99 -36
- package/dist/index.js +99 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This project provides utilities to generate patient summaries from FHIR Bundles, including narrative generation and section extraction, following the International Patient Summary (IPS) specification.
|
|
4
4
|
|
|
5
|
+
Detailed explanation of content of each section of IPS is [here](./sections.md)
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
Clone the repository and install dependencies:
|
package/dist/index.cjs
CHANGED
|
@@ -103,6 +103,11 @@ var SOCIAL_HISTORY_LOINC_CODES = {
|
|
|
103
103
|
"72166-2": "Tobacco Use",
|
|
104
104
|
"74013-4": "Alcohol Use"
|
|
105
105
|
};
|
|
106
|
+
var BLOOD_PRESSURE_LOINC_CODES = {
|
|
107
|
+
OBSERVATION: "85354-9",
|
|
108
|
+
SYSTOLIC: "8480-6",
|
|
109
|
+
DIASTOLIC: "8462-4"
|
|
110
|
+
};
|
|
106
111
|
|
|
107
112
|
// src/structures/ips_section_resource_map.ts
|
|
108
113
|
var IPSSectionResourceMap = {
|
|
@@ -126,9 +131,7 @@ var IPSSectionResourceMap = {
|
|
|
126
131
|
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: ["Consent"]
|
|
127
132
|
};
|
|
128
133
|
var IPSSectionResourceFilters = {
|
|
129
|
-
// Only include active
|
|
130
|
-
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "AllergyIntolerance" && resource.clinicalStatus?.coding?.some((c) => typeof c.code === "string"),
|
|
131
|
-
// Only include active problems/conditions
|
|
134
|
+
// Only include active conditions
|
|
132
135
|
["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
|
|
133
136
|
// Only include completed immunizations
|
|
134
137
|
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
|
|
@@ -138,12 +141,12 @@ var IPSSectionResourceFilters = {
|
|
|
138
141
|
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => ["DiagnosticReport", "Observation"].includes(resource.resourceType) && resource.status === "final",
|
|
139
142
|
// Only include completed procedures
|
|
140
143
|
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
|
|
141
|
-
// Only include social history Observations
|
|
144
|
+
// Only include social history Observations
|
|
142
145
|
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && resource.code?.coding?.some((c) => Object.keys(SOCIAL_HISTORY_LOINC_CODES).includes(c.code)),
|
|
143
|
-
// Only include pregnancy history Observations
|
|
146
|
+
// Only include pregnancy history Observations
|
|
144
147
|
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (resource.code?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS).includes(c.code)) || resource.valueCodeableConcept?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME).includes(c.code))),
|
|
145
|
-
// Only include
|
|
146
|
-
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition"
|
|
148
|
+
// Only include Conditions or completed ClinicalImpressions
|
|
149
|
+
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
|
|
147
150
|
// Only include resolved medical history Conditions
|
|
148
151
|
["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
|
|
149
152
|
// Only include active care plans
|
|
@@ -532,6 +535,28 @@ var TemplateUtilities = class {
|
|
|
532
535
|
return "";
|
|
533
536
|
}
|
|
534
537
|
extractObservationValue(observation) {
|
|
538
|
+
if (observation.code && observation.code.coding && "component" in observation && Array.isArray(observation.component)) {
|
|
539
|
+
const bpCode = observation.code.coding.find(
|
|
540
|
+
(c) => c.code === BLOOD_PRESSURE_LOINC_CODES.OBSERVATION
|
|
541
|
+
);
|
|
542
|
+
if (bpCode) {
|
|
543
|
+
const systolicComponent = observation.component?.find(
|
|
544
|
+
(c) => c.code?.coding?.some(
|
|
545
|
+
(cc) => cc.code === BLOOD_PRESSURE_LOINC_CODES.SYSTOLIC
|
|
546
|
+
)
|
|
547
|
+
);
|
|
548
|
+
const diastolicComponent = observation.component?.find(
|
|
549
|
+
(c) => c.code?.coding?.some(
|
|
550
|
+
(cc) => cc.code === BLOOD_PRESSURE_LOINC_CODES.DIASTOLIC
|
|
551
|
+
)
|
|
552
|
+
);
|
|
553
|
+
if (systolicComponent && diastolicComponent) {
|
|
554
|
+
const systolic = this.extractObservationValue(systolicComponent);
|
|
555
|
+
const diastolic = this.extractObservationValue(diastolicComponent);
|
|
556
|
+
return `${systolic}/${diastolic}`;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
535
560
|
const valueFields = [
|
|
536
561
|
"valueString",
|
|
537
562
|
"valueInteger",
|
|
@@ -854,7 +879,9 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
854
879
|
patient.name.forEach((name) => {
|
|
855
880
|
if (name.use !== "old") {
|
|
856
881
|
const nameText = name.text || ((name.given || []).join(" ") + " " + (name.family || "")).trim();
|
|
857
|
-
|
|
882
|
+
if (nameText) {
|
|
883
|
+
uniqueNames.add(nameText);
|
|
884
|
+
}
|
|
858
885
|
}
|
|
859
886
|
});
|
|
860
887
|
return Array.from(uniqueNames).map((nameText) => `<ul><li>${nameText}</li></ul>`).join("");
|
|
@@ -884,9 +911,10 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
884
911
|
return "";
|
|
885
912
|
}
|
|
886
913
|
const systemPriority = ["email", "phone", "pager", "sms", "fax", "url", "other"];
|
|
914
|
+
const numberSystems = ["phone", "pager", "sms", "fax"];
|
|
887
915
|
const telecomBySystem = /* @__PURE__ */ new Map();
|
|
888
916
|
patient.telecom.forEach((telecom) => {
|
|
889
|
-
if (telecom.system && telecom.value) {
|
|
917
|
+
if (telecom.system && telecom.value && telecom.use !== "old") {
|
|
890
918
|
const system = telecom.system.toLowerCase();
|
|
891
919
|
if (!telecomBySystem.has(system)) {
|
|
892
920
|
telecomBySystem.set(system, /* @__PURE__ */ new Set());
|
|
@@ -894,6 +922,29 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
894
922
|
telecomBySystem.get(system).add(telecom.value);
|
|
895
923
|
}
|
|
896
924
|
});
|
|
925
|
+
for (const system of numberSystems) {
|
|
926
|
+
const currentNumbers = Array.from(telecomBySystem.get(system) || []);
|
|
927
|
+
if (currentNumbers.length <= 1) continue;
|
|
928
|
+
const numbersWithCleaned = currentNumbers.map((num) => ({
|
|
929
|
+
original: num,
|
|
930
|
+
cleaned: num.replace(/\D/g, "")
|
|
931
|
+
}));
|
|
932
|
+
const toRemove = /* @__PURE__ */ new Set();
|
|
933
|
+
for (let i = 0; i < numbersWithCleaned.length; i++) {
|
|
934
|
+
for (let j = i + 1; j < numbersWithCleaned.length; j++) {
|
|
935
|
+
const num1 = numbersWithCleaned[i];
|
|
936
|
+
const num2 = numbersWithCleaned[j];
|
|
937
|
+
if (num1.cleaned.endsWith(num2.cleaned)) {
|
|
938
|
+
toRemove.add(num2.original);
|
|
939
|
+
} else if (num2.cleaned.endsWith(num1.cleaned)) {
|
|
940
|
+
toRemove.add(num1.original);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
toRemove.forEach((numberToRemove) => {
|
|
945
|
+
telecomBySystem.get(system)?.delete(numberToRemove);
|
|
946
|
+
});
|
|
947
|
+
}
|
|
897
948
|
return Array.from(telecomBySystem.entries()).sort(([systemA], [systemB]) => {
|
|
898
949
|
const priorityA = systemPriority.indexOf(systemA);
|
|
899
950
|
const priorityB = systemPriority.indexOf(systemB);
|
|
@@ -920,7 +971,24 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
920
971
|
}
|
|
921
972
|
const uniqueAddresses = /* @__PURE__ */ new Set();
|
|
922
973
|
patient.address.forEach((address) => {
|
|
923
|
-
|
|
974
|
+
if (address.use === "old") {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const addressArray = [];
|
|
978
|
+
if (address.text) {
|
|
979
|
+
addressArray.push(address.text);
|
|
980
|
+
} else {
|
|
981
|
+
if (address.line) {
|
|
982
|
+
addressArray.push(...address.line);
|
|
983
|
+
}
|
|
984
|
+
if (address.city) {
|
|
985
|
+
addressArray.push(address.city);
|
|
986
|
+
}
|
|
987
|
+
if (address.country) {
|
|
988
|
+
addressArray.push(address.country);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
const addressText = addressArray.join(", ").trim();
|
|
924
992
|
if (addressText) {
|
|
925
993
|
uniqueAddresses.add(addressText);
|
|
926
994
|
}
|
|
@@ -951,12 +1019,18 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
951
1019
|
if (!patient.communication || patient.communication.length === 0) {
|
|
952
1020
|
return "";
|
|
953
1021
|
}
|
|
954
|
-
|
|
955
|
-
|
|
1022
|
+
const uniqueLanguages = /* @__PURE__ */ new Set();
|
|
1023
|
+
const preferredLanguages = /* @__PURE__ */ new Set();
|
|
1024
|
+
patient.communication.forEach((comm) => {
|
|
956
1025
|
const language = templateUtilities.codeableConcept(comm.language);
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1026
|
+
if (language) {
|
|
1027
|
+
if (comm.preferred) {
|
|
1028
|
+
preferredLanguages.add(language);
|
|
1029
|
+
}
|
|
1030
|
+
uniqueLanguages.add(language);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
return Array.from(uniqueLanguages).map((language) => `<ul><li>${language}${preferredLanguages.has(language) ? " (preferred)" : ""}</li></ul>`).join("");
|
|
960
1034
|
}
|
|
961
1035
|
/**
|
|
962
1036
|
* Capitalizes first letter of a string
|
|
@@ -993,8 +1067,8 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
993
1067
|
if (resource.entry && Array.isArray(resource.entry)) {
|
|
994
1068
|
for (const entry of resource.entry) {
|
|
995
1069
|
const allergy = entry.resource;
|
|
996
|
-
const
|
|
997
|
-
if (
|
|
1070
|
+
const isResolved = allergy.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code));
|
|
1071
|
+
if (isResolved) {
|
|
998
1072
|
resolvedAllergies.push(allergy);
|
|
999
1073
|
} else {
|
|
1000
1074
|
activeAllergies.push(allergy);
|
|
@@ -1022,7 +1096,6 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1022
1096
|
<th>Status</th>
|
|
1023
1097
|
<th>Category</th>
|
|
1024
1098
|
<th>Reaction</th>
|
|
1025
|
-
<th>Severity</th>
|
|
1026
1099
|
<th>Onset Date</th>
|
|
1027
1100
|
<th>Comments</th>
|
|
1028
1101
|
</tr>
|
|
@@ -1050,7 +1123,6 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1050
1123
|
<th>Status</th>
|
|
1051
1124
|
<th>Category</th>
|
|
1052
1125
|
<th>Reaction</th>
|
|
1053
|
-
<th>Severity</th>
|
|
1054
1126
|
<th>Onset Date</th>
|
|
1055
1127
|
<th>Comments</th>
|
|
1056
1128
|
<th>Resolved Date</th>
|
|
@@ -1088,7 +1160,6 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1088
1160
|
<td class="Status">${templateUtilities.codeableConcept(allergy.clinicalStatus) || "-"}</td>
|
|
1089
1161
|
<td class="Category">${templateUtilities.safeConcat(allergy.category) || "-"}</td>
|
|
1090
1162
|
<td class="Reaction">${templateUtilities.concatReactionManifestation(allergy.reaction) || "-"}</td>
|
|
1091
|
-
<td class="Severity">${templateUtilities.safeConcat(allergy.reaction, "severity") || "-"}</td>
|
|
1092
1163
|
<td class="OnsetDate">${templateUtilities.renderTime(allergy.onsetDateTime, timezone) || "-"}</td>
|
|
1093
1164
|
<td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
|
|
1094
1165
|
if (includeResolved) {
|
|
@@ -1238,11 +1309,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1238
1309
|
};
|
|
1239
1310
|
if (allActiveMedications.length > 0) {
|
|
1240
1311
|
sortMedications(allActiveMedications);
|
|
1241
|
-
html += this.renderCombinedMedications(templateUtilities, allActiveMedications,
|
|
1312
|
+
html += this.renderCombinedMedications(templateUtilities, allActiveMedications, true);
|
|
1242
1313
|
}
|
|
1243
1314
|
if (allInactiveMedications.length > 0) {
|
|
1244
1315
|
sortMedications(allInactiveMedications);
|
|
1245
|
-
html += this.renderCombinedMedications(templateUtilities, allInactiveMedications,
|
|
1316
|
+
html += this.renderCombinedMedications(templateUtilities, allInactiveMedications, false);
|
|
1246
1317
|
}
|
|
1247
1318
|
return html;
|
|
1248
1319
|
}
|
|
@@ -1283,9 +1354,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1283
1354
|
* @param sectionTitle - Title for the section
|
|
1284
1355
|
* @returns HTML string for rendering
|
|
1285
1356
|
*/
|
|
1286
|
-
static renderCombinedMedications(templateUtilities, medications,
|
|
1357
|
+
static renderCombinedMedications(templateUtilities, medications, isActiveSection) {
|
|
1287
1358
|
let html = `
|
|
1288
|
-
<h3>${
|
|
1359
|
+
<h3>${isActiveSection ? "Active Medications" : "Inactive Medications"}</h3>
|
|
1289
1360
|
<table>
|
|
1290
1361
|
<thead>
|
|
1291
1362
|
<tr>
|
|
@@ -1294,8 +1365,8 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1294
1365
|
<th>Sig</th>
|
|
1295
1366
|
<th>Dispense Quantity</th>
|
|
1296
1367
|
<th>Refills</th>
|
|
1297
|
-
<th>Start Date</th
|
|
1298
|
-
<th>End Date</th
|
|
1368
|
+
<th>Start Date</th>${isActiveSection ? "" : `
|
|
1369
|
+
<th>End Date</th>`}
|
|
1299
1370
|
<th>Status</th>
|
|
1300
1371
|
</tr>
|
|
1301
1372
|
</thead>
|
|
@@ -1353,8 +1424,8 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1353
1424
|
<td>${sig}</td>
|
|
1354
1425
|
<td>${dispenseQuantity}</td>
|
|
1355
1426
|
<td>${refills}</td>
|
|
1356
|
-
<td>${startDate}</td
|
|
1357
|
-
<td>${endDate}</td
|
|
1427
|
+
<td>${startDate}</td>${isActiveSection ? "" : `
|
|
1428
|
+
<td>${endDate}</td>`}
|
|
1358
1429
|
<td>${status}</td>
|
|
1359
1430
|
</tr>`;
|
|
1360
1431
|
}
|
|
@@ -1460,7 +1531,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1460
1531
|
<thead>
|
|
1461
1532
|
<tr>
|
|
1462
1533
|
<th>Problem</th>
|
|
1463
|
-
<th>Severity</th>
|
|
1464
1534
|
<th>Onset Date</th>
|
|
1465
1535
|
<th>Recorded Date</th>
|
|
1466
1536
|
<th>Notes</th>
|
|
@@ -1470,7 +1540,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1470
1540
|
for (const cond of activeConditions) {
|
|
1471
1541
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
1472
1542
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
1473
|
-
<td class="Severity">${templateUtilities.codeableConcept(cond.severity)}</td>
|
|
1474
1543
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
1475
1544
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
1476
1545
|
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
|
|
@@ -1714,7 +1783,6 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
1714
1783
|
<thead>
|
|
1715
1784
|
<tr>
|
|
1716
1785
|
<th>Report</th>
|
|
1717
|
-
<th>Status</th>
|
|
1718
1786
|
<th>Category</th>
|
|
1719
1787
|
<th>Result</th>
|
|
1720
1788
|
<th>Issued</th>
|
|
@@ -1729,7 +1797,6 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
1729
1797
|
html += `
|
|
1730
1798
|
<tr id="${templateUtilities.narrativeLinkId(report)}">
|
|
1731
1799
|
<td>${templateUtilities.codeableConcept(report.code)}</td>
|
|
1732
|
-
<td>${report.status || ""}</td>
|
|
1733
1800
|
<td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
|
|
1734
1801
|
<td>${resultCount}</td>
|
|
1735
1802
|
<td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
|
|
@@ -1870,7 +1937,6 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
1870
1937
|
<thead>
|
|
1871
1938
|
<tr>
|
|
1872
1939
|
<th>Problem</th>
|
|
1873
|
-
<th>Severity</th>
|
|
1874
1940
|
<th>Onset Date</th>
|
|
1875
1941
|
<th>Recorded Date</th>
|
|
1876
1942
|
<th>Resolved Date</th>
|
|
@@ -1881,7 +1947,6 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
1881
1947
|
for (const cond of resolvedConditions) {
|
|
1882
1948
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
1883
1949
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
1884
|
-
<td class="Severity">${templateUtilities.codeableConcept(cond.severity)}</td>
|
|
1885
1950
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
1886
1951
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
1887
1952
|
<td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
|
|
@@ -1992,7 +2057,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
1992
2057
|
<thead>
|
|
1993
2058
|
<tr>
|
|
1994
2059
|
<th>Problem</th>
|
|
1995
|
-
<th>Severity</th>
|
|
1996
2060
|
<th>Onset Date</th>
|
|
1997
2061
|
<th>Recorded Date</th>
|
|
1998
2062
|
<th>Notes</th>
|
|
@@ -2002,7 +2066,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2002
2066
|
for (const cond of activeConditions) {
|
|
2003
2067
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
2004
2068
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
2005
|
-
<td class="Severity">${templateUtilities.codeableConcept(cond.severity)}</td>
|
|
2006
2069
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
2007
2070
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
2008
2071
|
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone, { styled: true, warning: true })}</td>
|
package/dist/index.js
CHANGED
|
@@ -75,6 +75,11 @@ var SOCIAL_HISTORY_LOINC_CODES = {
|
|
|
75
75
|
"72166-2": "Tobacco Use",
|
|
76
76
|
"74013-4": "Alcohol Use"
|
|
77
77
|
};
|
|
78
|
+
var BLOOD_PRESSURE_LOINC_CODES = {
|
|
79
|
+
OBSERVATION: "85354-9",
|
|
80
|
+
SYSTOLIC: "8480-6",
|
|
81
|
+
DIASTOLIC: "8462-4"
|
|
82
|
+
};
|
|
78
83
|
|
|
79
84
|
// src/structures/ips_section_resource_map.ts
|
|
80
85
|
var IPSSectionResourceMap = {
|
|
@@ -98,9 +103,7 @@ var IPSSectionResourceMap = {
|
|
|
98
103
|
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: ["Consent"]
|
|
99
104
|
};
|
|
100
105
|
var IPSSectionResourceFilters = {
|
|
101
|
-
// Only include active
|
|
102
|
-
["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "AllergyIntolerance" && resource.clinicalStatus?.coding?.some((c) => typeof c.code === "string"),
|
|
103
|
-
// Only include active problems/conditions
|
|
106
|
+
// Only include active conditions
|
|
104
107
|
["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
|
|
105
108
|
// Only include completed immunizations
|
|
106
109
|
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
|
|
@@ -110,12 +113,12 @@ var IPSSectionResourceFilters = {
|
|
|
110
113
|
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => ["DiagnosticReport", "Observation"].includes(resource.resourceType) && resource.status === "final",
|
|
111
114
|
// Only include completed procedures
|
|
112
115
|
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
|
|
113
|
-
// Only include social history Observations
|
|
116
|
+
// Only include social history Observations
|
|
114
117
|
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && resource.code?.coding?.some((c) => Object.keys(SOCIAL_HISTORY_LOINC_CODES).includes(c.code)),
|
|
115
|
-
// Only include pregnancy history Observations
|
|
118
|
+
// Only include pregnancy history Observations
|
|
116
119
|
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (resource.code?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS).includes(c.code)) || resource.valueCodeableConcept?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME).includes(c.code))),
|
|
117
|
-
// Only include
|
|
118
|
-
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition"
|
|
120
|
+
// Only include Conditions or completed ClinicalImpressions
|
|
121
|
+
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
|
|
119
122
|
// Only include resolved medical history Conditions
|
|
120
123
|
["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
|
|
121
124
|
// Only include active care plans
|
|
@@ -504,6 +507,28 @@ var TemplateUtilities = class {
|
|
|
504
507
|
return "";
|
|
505
508
|
}
|
|
506
509
|
extractObservationValue(observation) {
|
|
510
|
+
if (observation.code && observation.code.coding && "component" in observation && Array.isArray(observation.component)) {
|
|
511
|
+
const bpCode = observation.code.coding.find(
|
|
512
|
+
(c) => c.code === BLOOD_PRESSURE_LOINC_CODES.OBSERVATION
|
|
513
|
+
);
|
|
514
|
+
if (bpCode) {
|
|
515
|
+
const systolicComponent = observation.component?.find(
|
|
516
|
+
(c) => c.code?.coding?.some(
|
|
517
|
+
(cc) => cc.code === BLOOD_PRESSURE_LOINC_CODES.SYSTOLIC
|
|
518
|
+
)
|
|
519
|
+
);
|
|
520
|
+
const diastolicComponent = observation.component?.find(
|
|
521
|
+
(c) => c.code?.coding?.some(
|
|
522
|
+
(cc) => cc.code === BLOOD_PRESSURE_LOINC_CODES.DIASTOLIC
|
|
523
|
+
)
|
|
524
|
+
);
|
|
525
|
+
if (systolicComponent && diastolicComponent) {
|
|
526
|
+
const systolic = this.extractObservationValue(systolicComponent);
|
|
527
|
+
const diastolic = this.extractObservationValue(diastolicComponent);
|
|
528
|
+
return `${systolic}/${diastolic}`;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
507
532
|
const valueFields = [
|
|
508
533
|
"valueString",
|
|
509
534
|
"valueInteger",
|
|
@@ -826,7 +851,9 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
826
851
|
patient.name.forEach((name) => {
|
|
827
852
|
if (name.use !== "old") {
|
|
828
853
|
const nameText = name.text || ((name.given || []).join(" ") + " " + (name.family || "")).trim();
|
|
829
|
-
|
|
854
|
+
if (nameText) {
|
|
855
|
+
uniqueNames.add(nameText);
|
|
856
|
+
}
|
|
830
857
|
}
|
|
831
858
|
});
|
|
832
859
|
return Array.from(uniqueNames).map((nameText) => `<ul><li>${nameText}</li></ul>`).join("");
|
|
@@ -856,9 +883,10 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
856
883
|
return "";
|
|
857
884
|
}
|
|
858
885
|
const systemPriority = ["email", "phone", "pager", "sms", "fax", "url", "other"];
|
|
886
|
+
const numberSystems = ["phone", "pager", "sms", "fax"];
|
|
859
887
|
const telecomBySystem = /* @__PURE__ */ new Map();
|
|
860
888
|
patient.telecom.forEach((telecom) => {
|
|
861
|
-
if (telecom.system && telecom.value) {
|
|
889
|
+
if (telecom.system && telecom.value && telecom.use !== "old") {
|
|
862
890
|
const system = telecom.system.toLowerCase();
|
|
863
891
|
if (!telecomBySystem.has(system)) {
|
|
864
892
|
telecomBySystem.set(system, /* @__PURE__ */ new Set());
|
|
@@ -866,6 +894,29 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
866
894
|
telecomBySystem.get(system).add(telecom.value);
|
|
867
895
|
}
|
|
868
896
|
});
|
|
897
|
+
for (const system of numberSystems) {
|
|
898
|
+
const currentNumbers = Array.from(telecomBySystem.get(system) || []);
|
|
899
|
+
if (currentNumbers.length <= 1) continue;
|
|
900
|
+
const numbersWithCleaned = currentNumbers.map((num) => ({
|
|
901
|
+
original: num,
|
|
902
|
+
cleaned: num.replace(/\D/g, "")
|
|
903
|
+
}));
|
|
904
|
+
const toRemove = /* @__PURE__ */ new Set();
|
|
905
|
+
for (let i = 0; i < numbersWithCleaned.length; i++) {
|
|
906
|
+
for (let j = i + 1; j < numbersWithCleaned.length; j++) {
|
|
907
|
+
const num1 = numbersWithCleaned[i];
|
|
908
|
+
const num2 = numbersWithCleaned[j];
|
|
909
|
+
if (num1.cleaned.endsWith(num2.cleaned)) {
|
|
910
|
+
toRemove.add(num2.original);
|
|
911
|
+
} else if (num2.cleaned.endsWith(num1.cleaned)) {
|
|
912
|
+
toRemove.add(num1.original);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
toRemove.forEach((numberToRemove) => {
|
|
917
|
+
telecomBySystem.get(system)?.delete(numberToRemove);
|
|
918
|
+
});
|
|
919
|
+
}
|
|
869
920
|
return Array.from(telecomBySystem.entries()).sort(([systemA], [systemB]) => {
|
|
870
921
|
const priorityA = systemPriority.indexOf(systemA);
|
|
871
922
|
const priorityB = systemPriority.indexOf(systemB);
|
|
@@ -892,7 +943,24 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
892
943
|
}
|
|
893
944
|
const uniqueAddresses = /* @__PURE__ */ new Set();
|
|
894
945
|
patient.address.forEach((address) => {
|
|
895
|
-
|
|
946
|
+
if (address.use === "old") {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const addressArray = [];
|
|
950
|
+
if (address.text) {
|
|
951
|
+
addressArray.push(address.text);
|
|
952
|
+
} else {
|
|
953
|
+
if (address.line) {
|
|
954
|
+
addressArray.push(...address.line);
|
|
955
|
+
}
|
|
956
|
+
if (address.city) {
|
|
957
|
+
addressArray.push(address.city);
|
|
958
|
+
}
|
|
959
|
+
if (address.country) {
|
|
960
|
+
addressArray.push(address.country);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
const addressText = addressArray.join(", ").trim();
|
|
896
964
|
if (addressText) {
|
|
897
965
|
uniqueAddresses.add(addressText);
|
|
898
966
|
}
|
|
@@ -923,12 +991,18 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
923
991
|
if (!patient.communication || patient.communication.length === 0) {
|
|
924
992
|
return "";
|
|
925
993
|
}
|
|
926
|
-
|
|
927
|
-
|
|
994
|
+
const uniqueLanguages = /* @__PURE__ */ new Set();
|
|
995
|
+
const preferredLanguages = /* @__PURE__ */ new Set();
|
|
996
|
+
patient.communication.forEach((comm) => {
|
|
928
997
|
const language = templateUtilities.codeableConcept(comm.language);
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
998
|
+
if (language) {
|
|
999
|
+
if (comm.preferred) {
|
|
1000
|
+
preferredLanguages.add(language);
|
|
1001
|
+
}
|
|
1002
|
+
uniqueLanguages.add(language);
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
return Array.from(uniqueLanguages).map((language) => `<ul><li>${language}${preferredLanguages.has(language) ? " (preferred)" : ""}</li></ul>`).join("");
|
|
932
1006
|
}
|
|
933
1007
|
/**
|
|
934
1008
|
* Capitalizes first letter of a string
|
|
@@ -965,8 +1039,8 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
965
1039
|
if (resource.entry && Array.isArray(resource.entry)) {
|
|
966
1040
|
for (const entry of resource.entry) {
|
|
967
1041
|
const allergy = entry.resource;
|
|
968
|
-
const
|
|
969
|
-
if (
|
|
1042
|
+
const isResolved = allergy.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code));
|
|
1043
|
+
if (isResolved) {
|
|
970
1044
|
resolvedAllergies.push(allergy);
|
|
971
1045
|
} else {
|
|
972
1046
|
activeAllergies.push(allergy);
|
|
@@ -994,7 +1068,6 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
994
1068
|
<th>Status</th>
|
|
995
1069
|
<th>Category</th>
|
|
996
1070
|
<th>Reaction</th>
|
|
997
|
-
<th>Severity</th>
|
|
998
1071
|
<th>Onset Date</th>
|
|
999
1072
|
<th>Comments</th>
|
|
1000
1073
|
</tr>
|
|
@@ -1022,7 +1095,6 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1022
1095
|
<th>Status</th>
|
|
1023
1096
|
<th>Category</th>
|
|
1024
1097
|
<th>Reaction</th>
|
|
1025
|
-
<th>Severity</th>
|
|
1026
1098
|
<th>Onset Date</th>
|
|
1027
1099
|
<th>Comments</th>
|
|
1028
1100
|
<th>Resolved Date</th>
|
|
@@ -1060,7 +1132,6 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1060
1132
|
<td class="Status">${templateUtilities.codeableConcept(allergy.clinicalStatus) || "-"}</td>
|
|
1061
1133
|
<td class="Category">${templateUtilities.safeConcat(allergy.category) || "-"}</td>
|
|
1062
1134
|
<td class="Reaction">${templateUtilities.concatReactionManifestation(allergy.reaction) || "-"}</td>
|
|
1063
|
-
<td class="Severity">${templateUtilities.safeConcat(allergy.reaction, "severity") || "-"}</td>
|
|
1064
1135
|
<td class="OnsetDate">${templateUtilities.renderTime(allergy.onsetDateTime, timezone) || "-"}</td>
|
|
1065
1136
|
<td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
|
|
1066
1137
|
if (includeResolved) {
|
|
@@ -1210,11 +1281,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1210
1281
|
};
|
|
1211
1282
|
if (allActiveMedications.length > 0) {
|
|
1212
1283
|
sortMedications(allActiveMedications);
|
|
1213
|
-
html += this.renderCombinedMedications(templateUtilities, allActiveMedications,
|
|
1284
|
+
html += this.renderCombinedMedications(templateUtilities, allActiveMedications, true);
|
|
1214
1285
|
}
|
|
1215
1286
|
if (allInactiveMedications.length > 0) {
|
|
1216
1287
|
sortMedications(allInactiveMedications);
|
|
1217
|
-
html += this.renderCombinedMedications(templateUtilities, allInactiveMedications,
|
|
1288
|
+
html += this.renderCombinedMedications(templateUtilities, allInactiveMedications, false);
|
|
1218
1289
|
}
|
|
1219
1290
|
return html;
|
|
1220
1291
|
}
|
|
@@ -1255,9 +1326,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1255
1326
|
* @param sectionTitle - Title for the section
|
|
1256
1327
|
* @returns HTML string for rendering
|
|
1257
1328
|
*/
|
|
1258
|
-
static renderCombinedMedications(templateUtilities, medications,
|
|
1329
|
+
static renderCombinedMedications(templateUtilities, medications, isActiveSection) {
|
|
1259
1330
|
let html = `
|
|
1260
|
-
<h3>${
|
|
1331
|
+
<h3>${isActiveSection ? "Active Medications" : "Inactive Medications"}</h3>
|
|
1261
1332
|
<table>
|
|
1262
1333
|
<thead>
|
|
1263
1334
|
<tr>
|
|
@@ -1266,8 +1337,8 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1266
1337
|
<th>Sig</th>
|
|
1267
1338
|
<th>Dispense Quantity</th>
|
|
1268
1339
|
<th>Refills</th>
|
|
1269
|
-
<th>Start Date</th
|
|
1270
|
-
<th>End Date</th
|
|
1340
|
+
<th>Start Date</th>${isActiveSection ? "" : `
|
|
1341
|
+
<th>End Date</th>`}
|
|
1271
1342
|
<th>Status</th>
|
|
1272
1343
|
</tr>
|
|
1273
1344
|
</thead>
|
|
@@ -1325,8 +1396,8 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1325
1396
|
<td>${sig}</td>
|
|
1326
1397
|
<td>${dispenseQuantity}</td>
|
|
1327
1398
|
<td>${refills}</td>
|
|
1328
|
-
<td>${startDate}</td
|
|
1329
|
-
<td>${endDate}</td
|
|
1399
|
+
<td>${startDate}</td>${isActiveSection ? "" : `
|
|
1400
|
+
<td>${endDate}</td>`}
|
|
1330
1401
|
<td>${status}</td>
|
|
1331
1402
|
</tr>`;
|
|
1332
1403
|
}
|
|
@@ -1432,7 +1503,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1432
1503
|
<thead>
|
|
1433
1504
|
<tr>
|
|
1434
1505
|
<th>Problem</th>
|
|
1435
|
-
<th>Severity</th>
|
|
1436
1506
|
<th>Onset Date</th>
|
|
1437
1507
|
<th>Recorded Date</th>
|
|
1438
1508
|
<th>Notes</th>
|
|
@@ -1442,7 +1512,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1442
1512
|
for (const cond of activeConditions) {
|
|
1443
1513
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
1444
1514
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
1445
|
-
<td class="Severity">${templateUtilities.codeableConcept(cond.severity)}</td>
|
|
1446
1515
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
1447
1516
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
1448
1517
|
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
|
|
@@ -1686,7 +1755,6 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
1686
1755
|
<thead>
|
|
1687
1756
|
<tr>
|
|
1688
1757
|
<th>Report</th>
|
|
1689
|
-
<th>Status</th>
|
|
1690
1758
|
<th>Category</th>
|
|
1691
1759
|
<th>Result</th>
|
|
1692
1760
|
<th>Issued</th>
|
|
@@ -1701,7 +1769,6 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
1701
1769
|
html += `
|
|
1702
1770
|
<tr id="${templateUtilities.narrativeLinkId(report)}">
|
|
1703
1771
|
<td>${templateUtilities.codeableConcept(report.code)}</td>
|
|
1704
|
-
<td>${report.status || ""}</td>
|
|
1705
1772
|
<td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
|
|
1706
1773
|
<td>${resultCount}</td>
|
|
1707
1774
|
<td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
|
|
@@ -1842,7 +1909,6 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
1842
1909
|
<thead>
|
|
1843
1910
|
<tr>
|
|
1844
1911
|
<th>Problem</th>
|
|
1845
|
-
<th>Severity</th>
|
|
1846
1912
|
<th>Onset Date</th>
|
|
1847
1913
|
<th>Recorded Date</th>
|
|
1848
1914
|
<th>Resolved Date</th>
|
|
@@ -1853,7 +1919,6 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
1853
1919
|
for (const cond of resolvedConditions) {
|
|
1854
1920
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
1855
1921
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
1856
|
-
<td class="Severity">${templateUtilities.codeableConcept(cond.severity)}</td>
|
|
1857
1922
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
1858
1923
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
1859
1924
|
<td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
|
|
@@ -1964,7 +2029,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
1964
2029
|
<thead>
|
|
1965
2030
|
<tr>
|
|
1966
2031
|
<th>Problem</th>
|
|
1967
|
-
<th>Severity</th>
|
|
1968
2032
|
<th>Onset Date</th>
|
|
1969
2033
|
<th>Recorded Date</th>
|
|
1970
2034
|
<th>Notes</th>
|
|
@@ -1974,7 +2038,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
1974
2038
|
for (const cond of activeConditions) {
|
|
1975
2039
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
1976
2040
|
<td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
|
|
1977
|
-
<td class="Severity">${templateUtilities.codeableConcept(cond.severity)}</td>
|
|
1978
2041
|
<td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
|
|
1979
2042
|
<td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
|
|
1980
2043
|
<td class="Notes">${templateUtilities.renderNotes(cond.note, timezone, { styled: true, warning: true })}</td>
|