@imranq2/fhirpatientsummary 1.0.13 → 1.0.15

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
@@ -126,9 +126,7 @@ var IPSSectionResourceMap = {
126
126
  ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: ["Consent"]
127
127
  };
128
128
  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
129
+ // Only include active conditions
132
130
  ["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
133
131
  // Only include completed immunizations
134
132
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
@@ -138,12 +136,12 @@ var IPSSectionResourceFilters = {
138
136
  ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => ["DiagnosticReport", "Observation"].includes(resource.resourceType) && resource.status === "final",
139
137
  // Only include completed procedures
140
138
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
141
- // Only include social history Observations (category.coding contains 'social-history')
139
+ // Only include social history Observations
142
140
  ["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')
141
+ // Only include pregnancy history Observations
144
142
  ["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",
143
+ // Only include Conditions or completed ClinicalImpressions
144
+ ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
147
145
  // Only include resolved medical history Conditions
148
146
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
149
147
  // Only include active care plans
@@ -830,7 +828,7 @@ var PatientTemplate = class _PatientTemplate {
830
828
  <li><strong>Gender:</strong>${patient.gender ? this.capitalize(patient.gender) : ""}</li>
831
829
  <li><strong>Date of Birth:</strong>${patient.birthDate || ""}</li>
832
830
  <li><strong>Identifier(s):</strong>${this.renderIdentifiers(patient)}</li>
833
- <li><strong>Telecom:</strong>${this.renderTelecom(patient)}</li>
831
+ <li><strong>Telecom:</strong><ul>${this.renderTelecom(patient)}</ul></li>
834
832
  <li><strong>Address(es):</strong>${this.renderAddresses(patient)}</li>
835
833
  <li><strong>Marital Status:</strong> ${patient.maritalStatus?.text || ""}</li>
836
834
  <li><strong>Deceased:</strong>${this.renderDeceased(patient)}</li>
@@ -850,10 +848,14 @@ var PatientTemplate = class _PatientTemplate {
850
848
  if (!patient.name || patient.name.length === 0) {
851
849
  return "";
852
850
  }
853
- return patient.name.map((name) => {
854
- const nameText = name.text || ((name.given || []).join(" ") + " " + (name.family || "")).trim();
855
- return `<ul><li>${nameText}</li></ul>`;
856
- }).join("");
851
+ const uniqueNames = /* @__PURE__ */ new Set();
852
+ patient.name.forEach((name) => {
853
+ if (name.use !== "old") {
854
+ const nameText = name.text || ((name.given || []).join(" ") + " " + (name.family || "")).trim();
855
+ uniqueNames.add(nameText);
856
+ }
857
+ });
858
+ return Array.from(uniqueNames).map((nameText) => `<ul><li>${nameText}</li></ul>`).join("");
857
859
  }
858
860
  /**
859
861
  * Renders patient identifiers as HTML list items
@@ -871,19 +873,38 @@ var PatientTemplate = class _PatientTemplate {
871
873
  }).join("");
872
874
  }
873
875
  /**
874
- * Renders patient telecom information as HTML list items
876
+ * Renders patient telecom information grouped by system
875
877
  * @param patient - Patient resource
876
- * @returns HTML string of list items
878
+ * @returns HTML string grouped by system
877
879
  */
878
880
  static renderTelecom(patient) {
879
881
  if (!patient.telecom || patient.telecom.length === 0) {
880
882
  return "";
881
883
  }
882
- return patient.telecom.map((telecom) => {
883
- const system = telecom.system ? this.capitalize(telecom.system) : "";
884
- const value = telecom.value || "";
885
- const use = telecom.use ? ` (${telecom.use})` : "";
886
- return `<ul><li>${system}: ${value}${use}</li></ul>`;
884
+ const systemPriority = ["email", "phone", "pager", "sms", "fax", "url", "other"];
885
+ const telecomBySystem = /* @__PURE__ */ new Map();
886
+ patient.telecom.forEach((telecom) => {
887
+ if (telecom.system && telecom.value) {
888
+ const system = telecom.system.toLowerCase();
889
+ if (!telecomBySystem.has(system)) {
890
+ telecomBySystem.set(system, /* @__PURE__ */ new Set());
891
+ }
892
+ telecomBySystem.get(system).add(telecom.value);
893
+ }
894
+ });
895
+ return Array.from(telecomBySystem.entries()).sort(([systemA], [systemB]) => {
896
+ const priorityA = systemPriority.indexOf(systemA);
897
+ const priorityB = systemPriority.indexOf(systemB);
898
+ if (priorityA !== -1 && priorityB !== -1) {
899
+ return priorityA - priorityB;
900
+ }
901
+ if (priorityA !== -1) return -1;
902
+ if (priorityB !== -1) return 1;
903
+ return systemA.localeCompare(systemB);
904
+ }).map(([system, values]) => {
905
+ const systemLabel = this.capitalize(system);
906
+ const valueList = Array.from(values).map((value) => `<li>${value}</li>`).join("");
907
+ return `<li><strong>${systemLabel}:</strong><ul>${valueList}</ul></li>`;
887
908
  }).join("");
888
909
  }
889
910
  /**
@@ -895,10 +916,14 @@ var PatientTemplate = class _PatientTemplate {
895
916
  if (!patient.address || patient.address.length === 0) {
896
917
  return "";
897
918
  }
898
- return patient.address.map((address) => {
919
+ const uniqueAddresses = /* @__PURE__ */ new Set();
920
+ patient.address.forEach((address) => {
899
921
  const addressText = address.text || ((address.line || []).join(", ") + ", " + (address.city || "") + ", " + (address.country || "")).trim();
900
- return `<ul><li>${addressText}</li></ul>`;
901
- }).join("");
922
+ if (addressText) {
923
+ uniqueAddresses.add(addressText);
924
+ }
925
+ });
926
+ return Array.from(uniqueAddresses).map((addressText) => `<ul><li>${addressText}</li></ul>`).join("");
902
927
  }
903
928
  /**
904
929
  * Renders patient deceased status
@@ -966,8 +991,8 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
966
991
  if (resource.entry && Array.isArray(resource.entry)) {
967
992
  for (const entry of resource.entry) {
968
993
  const allergy = entry.resource;
969
- const status = allergy.clinicalStatus?.coding?.[0]?.code || "";
970
- if (status === "inactive" || status === "resolved") {
994
+ const isResolved = allergy.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code));
995
+ if (isResolved) {
971
996
  resolvedAllergies.push(allergy);
972
997
  } else {
973
998
  activeAllergies.push(allergy);
@@ -1642,7 +1667,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
1642
1667
  */
1643
1668
  static renderObservations(templateUtilities, observations, timezone) {
1644
1669
  let html = `
1645
- <h5>Observations</h5>
1670
+ <h3>Observations</h3>
1646
1671
  <table>
1647
1672
  <thead>
1648
1673
  <tr>
@@ -1682,7 +1707,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
1682
1707
  */
1683
1708
  static renderDiagnosticReports(templateUtilities, reports, timezone) {
1684
1709
  let html = `
1685
- <h5>Diagnostic Reports</h5>
1710
+ <h3>Diagnostic Reports</h3>
1686
1711
  <table>
1687
1712
  <thead>
1688
1713
  <tr>
package/dist/index.js CHANGED
@@ -98,9 +98,7 @@ var IPSSectionResourceMap = {
98
98
  ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: ["Consent"]
99
99
  };
100
100
  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
101
+ // Only include active conditions
104
102
  ["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
105
103
  // Only include completed immunizations
106
104
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
@@ -110,12 +108,12 @@ var IPSSectionResourceFilters = {
110
108
  ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => ["DiagnosticReport", "Observation"].includes(resource.resourceType) && resource.status === "final",
111
109
  // Only include completed procedures
112
110
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
113
- // Only include social history Observations (category.coding contains 'social-history')
111
+ // Only include social history Observations
114
112
  ["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')
113
+ // Only include pregnancy history Observations
116
114
  ["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",
115
+ // Only include Conditions or completed ClinicalImpressions
116
+ ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
119
117
  // Only include resolved medical history Conditions
120
118
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
121
119
  // Only include active care plans
@@ -802,7 +800,7 @@ var PatientTemplate = class _PatientTemplate {
802
800
  <li><strong>Gender:</strong>${patient.gender ? this.capitalize(patient.gender) : ""}</li>
803
801
  <li><strong>Date of Birth:</strong>${patient.birthDate || ""}</li>
804
802
  <li><strong>Identifier(s):</strong>${this.renderIdentifiers(patient)}</li>
805
- <li><strong>Telecom:</strong>${this.renderTelecom(patient)}</li>
803
+ <li><strong>Telecom:</strong><ul>${this.renderTelecom(patient)}</ul></li>
806
804
  <li><strong>Address(es):</strong>${this.renderAddresses(patient)}</li>
807
805
  <li><strong>Marital Status:</strong> ${patient.maritalStatus?.text || ""}</li>
808
806
  <li><strong>Deceased:</strong>${this.renderDeceased(patient)}</li>
@@ -822,10 +820,14 @@ var PatientTemplate = class _PatientTemplate {
822
820
  if (!patient.name || patient.name.length === 0) {
823
821
  return "";
824
822
  }
825
- return patient.name.map((name) => {
826
- const nameText = name.text || ((name.given || []).join(" ") + " " + (name.family || "")).trim();
827
- return `<ul><li>${nameText}</li></ul>`;
828
- }).join("");
823
+ const uniqueNames = /* @__PURE__ */ new Set();
824
+ patient.name.forEach((name) => {
825
+ if (name.use !== "old") {
826
+ const nameText = name.text || ((name.given || []).join(" ") + " " + (name.family || "")).trim();
827
+ uniqueNames.add(nameText);
828
+ }
829
+ });
830
+ return Array.from(uniqueNames).map((nameText) => `<ul><li>${nameText}</li></ul>`).join("");
829
831
  }
830
832
  /**
831
833
  * Renders patient identifiers as HTML list items
@@ -843,19 +845,38 @@ var PatientTemplate = class _PatientTemplate {
843
845
  }).join("");
844
846
  }
845
847
  /**
846
- * Renders patient telecom information as HTML list items
848
+ * Renders patient telecom information grouped by system
847
849
  * @param patient - Patient resource
848
- * @returns HTML string of list items
850
+ * @returns HTML string grouped by system
849
851
  */
850
852
  static renderTelecom(patient) {
851
853
  if (!patient.telecom || patient.telecom.length === 0) {
852
854
  return "";
853
855
  }
854
- return patient.telecom.map((telecom) => {
855
- const system = telecom.system ? this.capitalize(telecom.system) : "";
856
- const value = telecom.value || "";
857
- const use = telecom.use ? ` (${telecom.use})` : "";
858
- return `<ul><li>${system}: ${value}${use}</li></ul>`;
856
+ const systemPriority = ["email", "phone", "pager", "sms", "fax", "url", "other"];
857
+ const telecomBySystem = /* @__PURE__ */ new Map();
858
+ patient.telecom.forEach((telecom) => {
859
+ if (telecom.system && telecom.value) {
860
+ const system = telecom.system.toLowerCase();
861
+ if (!telecomBySystem.has(system)) {
862
+ telecomBySystem.set(system, /* @__PURE__ */ new Set());
863
+ }
864
+ telecomBySystem.get(system).add(telecom.value);
865
+ }
866
+ });
867
+ return Array.from(telecomBySystem.entries()).sort(([systemA], [systemB]) => {
868
+ const priorityA = systemPriority.indexOf(systemA);
869
+ const priorityB = systemPriority.indexOf(systemB);
870
+ if (priorityA !== -1 && priorityB !== -1) {
871
+ return priorityA - priorityB;
872
+ }
873
+ if (priorityA !== -1) return -1;
874
+ if (priorityB !== -1) return 1;
875
+ return systemA.localeCompare(systemB);
876
+ }).map(([system, values]) => {
877
+ const systemLabel = this.capitalize(system);
878
+ const valueList = Array.from(values).map((value) => `<li>${value}</li>`).join("");
879
+ return `<li><strong>${systemLabel}:</strong><ul>${valueList}</ul></li>`;
859
880
  }).join("");
860
881
  }
861
882
  /**
@@ -867,10 +888,14 @@ var PatientTemplate = class _PatientTemplate {
867
888
  if (!patient.address || patient.address.length === 0) {
868
889
  return "";
869
890
  }
870
- return patient.address.map((address) => {
891
+ const uniqueAddresses = /* @__PURE__ */ new Set();
892
+ patient.address.forEach((address) => {
871
893
  const addressText = address.text || ((address.line || []).join(", ") + ", " + (address.city || "") + ", " + (address.country || "")).trim();
872
- return `<ul><li>${addressText}</li></ul>`;
873
- }).join("");
894
+ if (addressText) {
895
+ uniqueAddresses.add(addressText);
896
+ }
897
+ });
898
+ return Array.from(uniqueAddresses).map((addressText) => `<ul><li>${addressText}</li></ul>`).join("");
874
899
  }
875
900
  /**
876
901
  * Renders patient deceased status
@@ -938,8 +963,8 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
938
963
  if (resource.entry && Array.isArray(resource.entry)) {
939
964
  for (const entry of resource.entry) {
940
965
  const allergy = entry.resource;
941
- const status = allergy.clinicalStatus?.coding?.[0]?.code || "";
942
- if (status === "inactive" || status === "resolved") {
966
+ const isResolved = allergy.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code));
967
+ if (isResolved) {
943
968
  resolvedAllergies.push(allergy);
944
969
  } else {
945
970
  activeAllergies.push(allergy);
@@ -1614,7 +1639,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
1614
1639
  */
1615
1640
  static renderObservations(templateUtilities, observations, timezone) {
1616
1641
  let html = `
1617
- <h5>Observations</h5>
1642
+ <h3>Observations</h3>
1618
1643
  <table>
1619
1644
  <thead>
1620
1645
  <tr>
@@ -1654,7 +1679,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
1654
1679
  */
1655
1680
  static renderDiagnosticReports(templateUtilities, reports, timezone) {
1656
1681
  let html = `
1657
- <h5>Diagnostic Reports</h5>
1682
+ <h3>Diagnostic Reports</h3>
1658
1683
  <table>
1659
1684
  <thead>
1660
1685
  <tr>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imranq2/fhirpatientsummary",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
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",