@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 +2 -0
- package/dist/index.cjs +51 -26
- package/dist/index.js +51 -26
- 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
|
@@ -126,9 +126,7 @@ var IPSSectionResourceMap = {
|
|
|
126
126
|
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: ["Consent"]
|
|
127
127
|
};
|
|
128
128
|
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
|
|
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
|
|
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
|
|
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
|
|
146
|
-
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition"
|
|
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
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
|
876
|
+
* Renders patient telecom information grouped by system
|
|
875
877
|
* @param patient - Patient resource
|
|
876
|
-
* @returns HTML string
|
|
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
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
-
|
|
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
|
-
|
|
901
|
-
|
|
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
|
|
970
|
-
if (
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
|
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
|
|
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
|
|
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
|
|
118
|
-
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition"
|
|
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
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
|
848
|
+
* Renders patient telecom information grouped by system
|
|
847
849
|
* @param patient - Patient resource
|
|
848
|
-
* @returns HTML string
|
|
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
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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
|
-
|
|
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
|
-
|
|
873
|
-
|
|
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
|
|
942
|
-
if (
|
|
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
|
-
<
|
|
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
|
-
<
|
|
1682
|
+
<h3>Diagnostic Reports</h3>
|
|
1658
1683
|
<table>
|
|
1659
1684
|
<thead>
|
|
1660
1685
|
<tr>
|