@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 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 allergies
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 (category.coding contains 'social-history')
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 (category.coding contains 'pregnancy')
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 active functional status Conditions or ClinicalImpressions
146
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => typeof c.code === "string") || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
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
- uniqueNames.add(nameText);
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
- const addressText = address.text || ((address.line || []).join(", ") + ", " + (address.city || "") + ", " + (address.country || "")).trim();
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
- return patient.communication.map((comm) => {
955
- if (!comm.language) return "";
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
- const preferred = comm.preferred ? " (preferred)" : "";
958
- return `<ul><li>${language}${preferred}</li></ul>`;
959
- }).join("");
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 status = allergy.clinicalStatus?.coding?.[0]?.code || "";
997
- if (status === "inactive" || status === "resolved") {
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, "Active Medications");
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, "Inactive Medications");
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, sectionTitle) {
1357
+ static renderCombinedMedications(templateUtilities, medications, isActiveSection) {
1287
1358
  let html = `
1288
- <h3>${sectionTitle}</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 allergies
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 (category.coding contains 'social-history')
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 (category.coding contains 'pregnancy')
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 active functional status Conditions or ClinicalImpressions
118
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => typeof c.code === "string") || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
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
- uniqueNames.add(nameText);
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
- const addressText = address.text || ((address.line || []).join(", ") + ", " + (address.city || "") + ", " + (address.country || "")).trim();
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
- return patient.communication.map((comm) => {
927
- if (!comm.language) return "";
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
- const preferred = comm.preferred ? " (preferred)" : "";
930
- return `<ul><li>${language}${preferred}</li></ul>`;
931
- }).join("");
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 status = allergy.clinicalStatus?.coding?.[0]?.code || "";
969
- if (status === "inactive" || status === "resolved") {
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, "Active Medications");
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, "Inactive Medications");
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, sectionTitle) {
1329
+ static renderCombinedMedications(templateUtilities, medications, isActiveSection) {
1259
1330
  let html = `
1260
- <h3>${sectionTitle}</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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imranq2/fhirpatientsummary",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "A template for creating npm packages using TypeScript and VSCode",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",