@imranq2/fhirpatientsummary 1.0.16 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -110,33 +110,21 @@ var BLOOD_PRESSURE_LOINC_CODES = {
110
110
  };
111
111
 
112
112
  // src/structures/ips_section_resource_map.ts
113
- var IPSSectionResourceMap = {
114
- ["Patient" /* PATIENT */]: ["Patient"],
115
- ["AllergyIntoleranceSection" /* ALLERGIES */]: ["AllergyIntolerance"],
116
- ["MedicationSummarySection" /* MEDICATIONS */]: ["MedicationRequest", "MedicationStatement", "Medication"],
117
- // Medication resource is needed for identifying name of medication
118
- ["ProblemSection" /* PROBLEMS */]: ["Condition"],
119
- ["ImmunizationSection" /* IMMUNIZATIONS */]: ["Immunization", "Organization"],
120
- // Immunization can include Organization as a related resource
121
- ["VitalSignsSection" /* VITAL_SIGNS */]: ["Observation"],
122
- ["MedicalDeviceSection" /* MEDICAL_DEVICES */]: ["DeviceUseStatement", "Device"],
123
- // Device resource is used for medical devices name
124
- ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: ["DiagnosticReport", "Observation"],
125
- ["HistoryOfProceduresSection" /* PROCEDURES */]: ["Procedure"],
126
- ["SocialHistorySection" /* SOCIAL_HISTORY */]: ["Observation"],
127
- ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: ["Observation"],
128
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: ["Condition", "ClinicalImpression"],
129
- ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: ["Condition"],
130
- ["PlanOfCareSection" /* CARE_PLAN */]: ["CarePlan"],
131
- ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: ["Consent"]
132
- };
133
113
  var IPSSectionResourceFilters = {
114
+ // Patient section: only Patient resource
115
+ ["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Patient",
116
+ // Only include allergies
117
+ ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "AllergyIntolerance",
118
+ // includes MedicationRequest, MedicationStatement. Medication is needed for medication names
119
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement", "Medication"].includes(resource.resourceType),
134
120
  // Only include active conditions
135
121
  ["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
136
122
  // Only include completed immunizations
137
123
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
138
124
  // Only include vital sign Observations (category.coding contains 'vital-signs')
139
125
  ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => c.code === "vital-signs")),
126
+ // Includes DeviceUseStatement. Device is needed for linked device details
127
+ ["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
140
128
  // Only include finalized diagnostic reports
141
129
  ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => ["DiagnosticReport", "Observation"].includes(resource.resourceType) && resource.status === "final",
142
130
  // Only include completed procedures
@@ -152,28 +140,30 @@ var IPSSectionResourceFilters = {
152
140
  // Only include active care plans
153
141
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
154
142
  // Only include active advance directives (Consent resources)
155
- ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active",
156
- // Patient section: only Patient resource
157
- ["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Patient"
143
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
144
+ };
145
+ var IPSSectionSummaryCompositionFilter = {
146
+ ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "allergy_summary_document"),
147
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "vital_summary_document")
158
148
  };
159
149
  var IPSSectionResourceHelper = class {
160
- static getResourceTypesForSection(section) {
161
- return IPSSectionResourceMap[section] || [];
162
- }
163
150
  static getResourceFilterForSection(section) {
164
151
  return IPSSectionResourceFilters[section];
165
152
  }
153
+ static getSummaryCompositionFilterForSection(section) {
154
+ return IPSSectionSummaryCompositionFilter[section];
155
+ }
166
156
  };
167
157
 
168
158
  // src/narratives/templates/typescript/TemplateUtilities.ts
169
159
  var import_luxon = require("luxon");
170
160
  var TemplateUtilities = class {
171
161
  /**
172
- * Constructor to initialize the TemplateUtilities with a FHIR Bundle
173
- * @param bundle - FHIR Bundle containing resources
162
+ * Constructor to initialize the TemplateUtilities with a FHIR resources
163
+ * @param resources - FHIR resources
174
164
  */
175
- constructor(bundle) {
176
- this.bundle = bundle;
165
+ constructor(resources) {
166
+ this.resources = resources;
177
167
  }
178
168
  /**
179
169
  * Formats a CodeableConcept object
@@ -204,7 +194,7 @@ var TemplateUtilities = class {
204
194
  return "";
205
195
  }
206
196
  resolveReference(ref) {
207
- if (!ref || !this.bundle || !this.bundle.entry) {
197
+ if (!ref || !this.resources) {
208
198
  return null;
209
199
  }
210
200
  const referenceParts = ref.reference?.split("/");
@@ -213,10 +203,10 @@ var TemplateUtilities = class {
213
203
  }
214
204
  const referenceResourceType = referenceParts[0];
215
205
  const referenceResourceId = referenceParts[1];
216
- const resource = this.bundle.entry.find((entry) => {
217
- return entry.resource && entry.resource.resourceType === referenceResourceType && entry.resource.id === referenceResourceId;
206
+ const resource = this.resources.find((entry) => {
207
+ return entry.resourceType === referenceResourceType && entry.id === referenceResourceId;
218
208
  });
219
- return resource ? resource.resource : null;
209
+ return resource ? resource : null;
220
210
  }
221
211
  /**
222
212
  * Renders a Device reference
@@ -612,6 +602,137 @@ var TemplateUtilities = class {
612
602
  }
613
603
  return status;
614
604
  }
605
+ extractObservationSummaryValue(data, timezone) {
606
+ if (data["valueQuantity.value"] !== void 0) {
607
+ const value = data["valueQuantity.value"];
608
+ const unit = data["valueQuantity.unit"];
609
+ return unit ? `${value} ${unit}` : `${value}`;
610
+ }
611
+ if (data["valueCodeableConcept.text"] !== void 0) {
612
+ return data["valueCodeableConcept.text"];
613
+ }
614
+ if (data["valueCodeableConcept.coding.display"] !== void 0) {
615
+ return data["valueCodeableConcept.coding.display"];
616
+ }
617
+ if (data["valueString"] !== void 0) {
618
+ return data["valueString"];
619
+ }
620
+ if (data["valueBoolean"] !== void 0) {
621
+ return String(data["valueBoolean"]);
622
+ }
623
+ if (data["valueInteger"] !== void 0) {
624
+ return String(data["valueInteger"]);
625
+ }
626
+ if (data["valueDateTime"] !== void 0) {
627
+ return this.renderTime(data["valueDateTime"], timezone);
628
+ }
629
+ if (data["valuePeriod.start"] !== void 0 || data["valuePeriod.end"] !== void 0) {
630
+ const start = this.renderTime(data["valuePeriod.start"], timezone);
631
+ const end = this.renderTime(data["valuePeriod.end"], timezone);
632
+ if (start && end) {
633
+ return `${start} - ${end}`;
634
+ } else if (start) {
635
+ return `${start}`;
636
+ } else if (end) {
637
+ return `${end}`;
638
+ }
639
+ }
640
+ if (data["valueTime"] !== void 0) {
641
+ return this.renderTime(data["valueTime"], timezone);
642
+ }
643
+ if (data["valueSampledData.origin.value"] !== void 0 || data["valueSampledData.origin.unit"] !== void 0) {
644
+ const originValue = data["valueSampledData.origin.value"];
645
+ const originUnit = data["valueSampledData.origin.unit"];
646
+ let result = "";
647
+ if (originValue !== void 0 && originUnit !== void 0) {
648
+ result = `${originValue} ${originUnit}`;
649
+ } else if (originValue !== void 0) {
650
+ result = `${originValue}`;
651
+ } else if (originUnit !== void 0) {
652
+ result = `${originUnit}`;
653
+ }
654
+ const period = data["valueSampledData.period"];
655
+ const factor = data["valueSampledData.factor"];
656
+ const lowerLimit = data["valueSampledData.lowerLimit"];
657
+ const upperLimit = data["valueSampledData.upperLimit"];
658
+ const sampledData = data["valueSampledData.data"];
659
+ const extras = [];
660
+ if (period !== void 0) extras.push(`period: ${period}`);
661
+ if (factor !== void 0) extras.push(`factor: ${factor}`);
662
+ if (lowerLimit !== void 0) extras.push(`lowerLimit: ${lowerLimit}`);
663
+ if (upperLimit !== void 0) extras.push(`upperLimit: ${upperLimit}`);
664
+ if (sampledData !== void 0) extras.push(`data: ${sampledData}`);
665
+ if (extras.length > 0) {
666
+ result += ` (${extras.join(", ")})`;
667
+ }
668
+ return result;
669
+ }
670
+ if (data["valueRange.low.value"] !== void 0 || data["valueRange.high.value"] !== void 0) {
671
+ let referenceRange = "";
672
+ if (data["valueRange.low.value"] !== void 0) {
673
+ referenceRange += `${data["valueRange.low.value"]}`;
674
+ if (data["valueRange.low.unit"] !== void 0) {
675
+ referenceRange += ` ${data["valueRange.low.unit"]}`;
676
+ }
677
+ referenceRange = referenceRange.trim();
678
+ if (data["valueRange.high.value"] !== void 0) {
679
+ referenceRange += " - ";
680
+ }
681
+ }
682
+ if (data["valueRange.high.value"] !== void 0) {
683
+ referenceRange += `${data["valueRange.high.value"]}`;
684
+ if (data["valueRange.high.unit"] !== void 0) {
685
+ referenceRange += ` ${data["valueRange.high.unit"]}`;
686
+ }
687
+ }
688
+ return referenceRange.trim();
689
+ }
690
+ if (data["valueRatio.numerator.value"] !== void 0 || data["valueRatio.denominator.value"] !== void 0) {
691
+ let ratio = "";
692
+ if (data["valueRatio.numerator.value"] !== void 0) {
693
+ ratio += `${data["valueRatio.numerator.value"]}`;
694
+ if (data["valueRatio.numerator.unit"] !== void 0) {
695
+ ratio += ` ${data["valueRatio.numerator.unit"]}`;
696
+ }
697
+ }
698
+ if (data["valueRatio.denominator.value"] !== void 0) {
699
+ ratio += " / ";
700
+ ratio += `${data["valueRatio.denominator.value"]}`;
701
+ if (data["valueRatio.denominator.unit"] !== void 0) {
702
+ ratio += ` ${data["valueRatio.denominator.unit"]}`;
703
+ }
704
+ }
705
+ return ratio.trim();
706
+ }
707
+ return "";
708
+ }
709
+ extractObservationSummaryReferenceRange(data) {
710
+ let referenceRange = "";
711
+ if (data["referenceRange.low.value"]) {
712
+ referenceRange += `${data["referenceRange.low.value"]} ${data["referenceRange.low.unit"]}`;
713
+ referenceRange.trim();
714
+ if (data["referenceRange.high.value"]) {
715
+ referenceRange += " - ";
716
+ }
717
+ }
718
+ if (data["referenceRange.high.value"]) {
719
+ referenceRange += `${data["referenceRange.high.value"]} ${data["referenceRange.high.unit"]}`;
720
+ }
721
+ return referenceRange.trim();
722
+ }
723
+ extractObservationSummaryEffectiveTime(data, timezone) {
724
+ if (data["effectiveDateTime"]) {
725
+ return this.renderTime(data["effectiveDateTime"], timezone);
726
+ }
727
+ let effectiveTimePeriod = "";
728
+ if (data["effectivePeriod.start"]) {
729
+ effectiveTimePeriod += this.renderTime(data["effectivePeriod.start"], timezone);
730
+ }
731
+ if (data["effectivePeriod.end"]) {
732
+ effectiveTimePeriod += ` - ${this.renderTime(data["effectivePeriod.end"], timezone)}`;
733
+ }
734
+ return effectiveTimePeriod.trim();
735
+ }
615
736
  formatQuantityValue(quantity) {
616
737
  if (!quantity) return "";
617
738
  const parts = [];
@@ -787,7 +908,7 @@ var TemplateUtilities = class {
787
908
  if (parts.length === 2) {
788
909
  const resourceType = parts[0];
789
910
  const resourceId = parts[1];
790
- const resource = this.bundle.entry?.find((entry) => entry.resource?.resourceType === resourceType && entry.resource?.id === resourceId);
911
+ const resource = this.resources?.find((resource2) => resource2.resourceType === resourceType && resource2.id === resourceId);
791
912
  if (resource) {
792
913
  return `${resourceType}/${resourceId}`;
793
914
  }
@@ -827,48 +948,96 @@ var TemplateUtilities = class {
827
948
  // src/narratives/templates/typescript/PatientTemplate.ts
828
949
  var PatientTemplate = class _PatientTemplate {
829
950
  /**
830
- * Generate HTML narrative for Patient resource
831
- * @param resource - FHIR Bundle containing Patient resource
951
+ * Generate HTML narrative for Patient resources
952
+ * @param resources - FHIR Patient resources
832
953
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
833
954
  * @returns HTML string for rendering
834
955
  */
835
- generateNarrative(resource, timezone) {
836
- return _PatientTemplate.generateStaticNarrative(resource, timezone);
956
+ generateNarrative(resources, timezone) {
957
+ return _PatientTemplate.generateStaticNarrative(resources, timezone);
837
958
  }
838
959
  /**
839
960
  * Internal static implementation that actually generates the narrative
840
- * @param resource - FHIR Bundle containing Patient resource
961
+ * @param resources - FHIR Patient resources
841
962
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
842
963
  * @returns HTML string for rendering
843
964
  */
844
965
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
845
- static generateStaticNarrative(resource, timezone) {
846
- const templateUtilities = new TemplateUtilities(resource);
847
- let html = "";
848
- for (const entry of resource.entry || []) {
849
- if (entry.resource?.resourceType === "Patient") {
850
- const patient = entry.resource;
851
- html += `
852
- <div>
853
- <ul>
854
- <li><strong>Name(s):</strong>${this.renderNames(patient)}</li>
855
- <li><strong>Gender:</strong>${patient.gender ? this.capitalize(patient.gender) : ""}</li>
856
- <li><strong>Date of Birth:</strong>${patient.birthDate || ""}</li>
857
- <li><strong>Identifier(s):</strong>${this.renderIdentifiers(patient)}</li>
858
- <li><strong>Telecom:</strong><ul>${this.renderTelecom(patient)}</ul></li>
859
- <li><strong>Address(es):</strong>${this.renderAddresses(patient)}</li>
860
- <li><strong>Marital Status:</strong> ${patient.maritalStatus?.text || ""}</li>
861
- <li><strong>Deceased:</strong>${this.renderDeceased(patient)}</li>
862
- <li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, patient)}</li>
863
- </ul>
864
- </div>`;
966
+ static generateStaticNarrative(resources, timezone) {
967
+ const templateUtilities = new TemplateUtilities(resources);
968
+ const combinedPatient = this.combinePatients(resources);
969
+ return `<div>
970
+ <ul>
971
+ <li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
972
+ <li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
973
+ <li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
974
+ <li><strong>Identifier(s):</strong>${this.renderIdentifiers(combinedPatient)}</li>
975
+ <li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
976
+ <li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
977
+ <li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus?.text || ""}</li>
978
+ <li><strong>Deceased:</strong>${this.renderDeceased(combinedPatient)}</li>
979
+ <li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
980
+ </ul>
981
+ </div>`;
982
+ }
983
+ /**
984
+ * Combines multiple patient resources into a single patient object
985
+ * Merges fields, preferring non-empty values
986
+ * @param patients - Array of patient resources
987
+ * @returns Combined patient resource
988
+ */
989
+ static combinePatients(patients) {
990
+ if (patients.length === 1) {
991
+ return patients[0];
992
+ }
993
+ const combined = patients[0];
994
+ const allNames = [];
995
+ const allIdentifiers = [];
996
+ const allTelecom = [];
997
+ const allAddresses = [];
998
+ const allCommunication = [];
999
+ patients.forEach((patient) => {
1000
+ if (patient.name) {
1001
+ allNames.push(...patient.name);
865
1002
  }
866
- }
867
- return html;
1003
+ if (patient.identifier) {
1004
+ allIdentifiers.push(...patient.identifier);
1005
+ }
1006
+ if (patient.telecom) {
1007
+ allTelecom.push(...patient.telecom);
1008
+ }
1009
+ if (patient.address) {
1010
+ allAddresses.push(...patient.address);
1011
+ }
1012
+ if (patient.communication) {
1013
+ allCommunication.push(...patient.communication);
1014
+ }
1015
+ if (!combined.gender && patient.gender) {
1016
+ combined.gender = patient.gender;
1017
+ }
1018
+ if (!combined.birthDate && patient.birthDate) {
1019
+ combined.birthDate = patient.birthDate;
1020
+ }
1021
+ if (!combined.maritalStatus && patient.maritalStatus) {
1022
+ combined.maritalStatus = patient.maritalStatus;
1023
+ }
1024
+ if (!combined.deceasedBoolean && patient.deceasedBoolean !== void 0) {
1025
+ combined.deceasedBoolean = patient.deceasedBoolean;
1026
+ }
1027
+ if (!combined.deceasedDateTime && patient.deceasedDateTime) {
1028
+ combined.deceasedDateTime = patient.deceasedDateTime;
1029
+ }
1030
+ });
1031
+ combined.name = allNames;
1032
+ combined.identifier = allIdentifiers;
1033
+ combined.telecom = allTelecom;
1034
+ combined.address = allAddresses;
1035
+ combined.communication = allCommunication;
1036
+ return combined;
868
1037
  }
869
1038
  /**
870
1039
  * Renders patient names as HTML list items
871
- * @param patient - Patient resource
1040
+ * @param patient - Patient resources
872
1041
  * @returns HTML string of list items
873
1042
  */
874
1043
  static renderNames(patient) {
@@ -888,7 +1057,7 @@ var PatientTemplate = class _PatientTemplate {
888
1057
  }
889
1058
  /**
890
1059
  * Renders patient identifiers as HTML list items
891
- * @param patient - Patient resource
1060
+ * @param patient - Patient resources
892
1061
  * @returns HTML string of list items
893
1062
  */
894
1063
  static renderIdentifiers(patient) {
@@ -903,7 +1072,7 @@ var PatientTemplate = class _PatientTemplate {
903
1072
  }
904
1073
  /**
905
1074
  * Renders patient telecom information grouped by system
906
- * @param patient - Patient resource
1075
+ * @param patient - Patient resources
907
1076
  * @returns HTML string grouped by system
908
1077
  */
909
1078
  static renderTelecom(patient) {
@@ -962,7 +1131,7 @@ var PatientTemplate = class _PatientTemplate {
962
1131
  }
963
1132
  /**
964
1133
  * Renders patient addresses as HTML list items
965
- * @param patient - Patient resource
1134
+ * @param patient - Patient resources
966
1135
  * @returns HTML string of list items
967
1136
  */
968
1137
  static renderAddresses(patient) {
@@ -984,9 +1153,18 @@ var PatientTemplate = class _PatientTemplate {
984
1153
  if (address.city) {
985
1154
  addressArray.push(address.city);
986
1155
  }
1156
+ if (address.district) {
1157
+ addressArray.push(address.district);
1158
+ }
1159
+ if (address.state) {
1160
+ addressArray.push(address.state);
1161
+ }
987
1162
  if (address.country) {
988
1163
  addressArray.push(address.country);
989
1164
  }
1165
+ if (address.postalCode) {
1166
+ addressArray.push(address.postalCode);
1167
+ }
990
1168
  }
991
1169
  const addressText = addressArray.join(", ").trim();
992
1170
  if (addressText) {
@@ -997,7 +1175,7 @@ var PatientTemplate = class _PatientTemplate {
997
1175
  }
998
1176
  /**
999
1177
  * Renders patient deceased status
1000
- * @param patient - Patient resource
1178
+ * @param patient - Patient resources
1001
1179
  * @returns HTML string for deceased status
1002
1180
  */
1003
1181
  static renderDeceased(patient) {
@@ -1012,7 +1190,7 @@ var PatientTemplate = class _PatientTemplate {
1012
1190
  /**
1013
1191
  * Renders patient communication preferences as HTML list items
1014
1192
  * @param templateUtilities - Instance of TemplateUtilities for utility functions
1015
- * @param patient - Patient resource
1193
+ * @param patient - Patient resources
1016
1194
  * @returns HTML string of list items
1017
1195
  */
1018
1196
  static renderCommunication(templateUtilities, patient) {
@@ -1047,32 +1225,81 @@ var PatientTemplate = class _PatientTemplate {
1047
1225
  var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1048
1226
  /**
1049
1227
  * Generate HTML narrative for AllergyIntolerance resources
1050
- * @param resource - FHIR Bundle containing AllergyIntolerance resources
1228
+ * @param resources - FHIR resources array containing AllergyIntolerance resources
1051
1229
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1052
1230
  * @returns HTML string for rendering
1053
1231
  */
1054
- generateNarrative(resource, timezone) {
1055
- return _AllergyIntoleranceTemplate.generateStaticNarrative(resource, timezone);
1232
+ generateNarrative(resources, timezone) {
1233
+ return _AllergyIntoleranceTemplate.generateStaticNarrative(resources, timezone);
1234
+ }
1235
+ /**
1236
+ * Generate HTML narrative for AllergyIntolerance resources using summary
1237
+ * @param resources - FHIR Composition resources
1238
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1239
+ * @returns HTML string for rendering
1240
+ */
1241
+ generateSummaryNarrative(resources, timezone) {
1242
+ const templateUtilities = new TemplateUtilities(resources);
1243
+ let html = `
1244
+ <div>
1245
+ <table>
1246
+ <thead>
1247
+ <tr>
1248
+ <th>Allergen</th>
1249
+ <th>Criticality</th>
1250
+ <th>Recorded Date</th>
1251
+ </tr>
1252
+ </thead>
1253
+ <tbody>`;
1254
+ for (const resourceItem of resources) {
1255
+ for (const rowData of resourceItem.section ?? []) {
1256
+ const data = {};
1257
+ for (const columnData of rowData.section ?? []) {
1258
+ switch (columnData.title) {
1259
+ case "Allergen Name":
1260
+ data["allergen"] = columnData.text?.div ?? "";
1261
+ break;
1262
+ case "Criticality":
1263
+ data["criticality"] = columnData.text?.div ?? "";
1264
+ break;
1265
+ case "Recorded Date":
1266
+ data["recordedDate"] = columnData.text?.div ?? "";
1267
+ break;
1268
+ default:
1269
+ break;
1270
+ }
1271
+ }
1272
+ html += `
1273
+ <tr>
1274
+ <td>${data["allergen"] ?? "-"}</td>
1275
+ <td>${data["criticality"] ?? "-"}</td>
1276
+ <td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? "-"}</td>
1277
+ </tr>`;
1278
+ }
1279
+ }
1280
+ html += `
1281
+ </tbody>
1282
+ </table>
1283
+ </div>`;
1284
+ return html;
1056
1285
  }
1057
1286
  /**
1058
1287
  * Internal static implementation that actually generates the narrative
1059
- * @param resource - FHIR Bundle containing AllergyIntolerance resources
1288
+ * @param resources - FHIR resources array containing AllergyIntolerance resources
1060
1289
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1061
1290
  * @returns HTML string for rendering
1062
1291
  */
1063
- static generateStaticNarrative(resource, timezone) {
1064
- const templateUtilities = new TemplateUtilities(resource);
1292
+ static generateStaticNarrative(resources, timezone) {
1293
+ const templateUtilities = new TemplateUtilities(resources);
1065
1294
  const activeAllergies = [];
1066
1295
  const resolvedAllergies = [];
1067
- if (resource.entry && Array.isArray(resource.entry)) {
1068
- for (const entry of resource.entry) {
1069
- const allergy = entry.resource;
1070
- const isResolved = allergy.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code));
1071
- if (isResolved) {
1072
- resolvedAllergies.push(allergy);
1073
- } else {
1074
- activeAllergies.push(allergy);
1075
- }
1296
+ for (const resourceItem of resources) {
1297
+ const allergy = resourceItem;
1298
+ const isResolved = allergy.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code));
1299
+ if (isResolved) {
1300
+ resolvedAllergies.push(allergy);
1301
+ } else {
1302
+ activeAllergies.push(allergy);
1076
1303
  }
1077
1304
  }
1078
1305
  activeAllergies.sort((a, b) => {
@@ -1185,12 +1412,12 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1185
1412
  var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1186
1413
  /**
1187
1414
  * Generate HTML narrative for Medication resources
1188
- * @param resource - FHIR Bundle containing Medication resources
1415
+ * @param resources - FHIR Medication resources
1189
1416
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1190
1417
  * @returns HTML string for rendering
1191
1418
  */
1192
- generateNarrative(resource, timezone) {
1193
- return _MedicationSummaryTemplate.generateStaticNarrative(resource, timezone);
1419
+ generateNarrative(resources, timezone) {
1420
+ return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
1194
1421
  }
1195
1422
  /**
1196
1423
  * Safely parse a date string and return a valid Date object or null
@@ -1255,16 +1482,16 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1255
1482
  }
1256
1483
  /**
1257
1484
  * Internal static implementation that actually generates the narrative
1258
- * @param resource - FHIR Bundle containing Medication resources
1485
+ * @param resources - FHIR Medication resources
1259
1486
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1260
1487
  * @returns HTML string for rendering
1261
1488
  */
1262
1489
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1263
- static generateStaticNarrative(resource, timezone) {
1264
- const templateUtilities = new TemplateUtilities(resource);
1490
+ static generateStaticNarrative(resources, timezone) {
1491
+ const templateUtilities = new TemplateUtilities(resources);
1265
1492
  let html = "";
1266
- const medicationRequests = this.getMedicationRequests(templateUtilities, resource);
1267
- const medicationStatements = this.getMedicationStatements(templateUtilities, resource);
1493
+ const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
1494
+ const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
1268
1495
  const allActiveMedications = [];
1269
1496
  const allInactiveMedications = [];
1270
1497
  medicationRequests.forEach((mr) => {
@@ -1318,33 +1545,33 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1318
1545
  return html;
1319
1546
  }
1320
1547
  /**
1321
- * Extract MedicationRequest resources from the bundle
1548
+ * Extract MedicationRequest resources
1322
1549
  * @param templateUtilities - Instance of TemplateUtilities for utility functions
1323
- * @param resource - FHIR Bundle
1550
+ * @param resources - FHIR Medication resources
1324
1551
  * @returns Array of MedicationRequest resources
1325
1552
  */
1326
- static getMedicationRequests(templateUtilities, resource) {
1327
- if (!resource.entry || !Array.isArray(resource.entry)) {
1553
+ static getMedicationRequests(templateUtilities, resources) {
1554
+ if (resources.length === 0) {
1328
1555
  return [];
1329
1556
  }
1330
- return resource.entry.filter((entry) => entry.resource?.resourceType === "MedicationRequest").map((entry) => ({
1331
- resource: entry.resource,
1332
- extension: templateUtilities.narrativeLinkExtension(entry.resource)
1557
+ return resources.filter((entry) => entry.resourceType === "MedicationRequest").map((entry) => ({
1558
+ resource: entry,
1559
+ extension: templateUtilities.narrativeLinkExtension(entry)
1333
1560
  }));
1334
1561
  }
1335
1562
  /**
1336
- * Extract MedicationStatement resources from the bundle
1563
+ * Extract MedicationStatement resources
1337
1564
  * @param templateUtilities - Instance of TemplateUtilities for utility functions
1338
- * @param resource - FHIR Bundle
1565
+ * @param resources - FHIR Medication resources
1339
1566
  * @returns Array of MedicationStatement resources
1340
1567
  */
1341
- static getMedicationStatements(templateUtilities, resource) {
1342
- if (!resource.entry || !Array.isArray(resource.entry)) {
1568
+ static getMedicationStatements(templateUtilities, resources) {
1569
+ if (resources.length === 0) {
1343
1570
  return [];
1344
1571
  }
1345
- return resource.entry.filter((entry) => entry.resource?.resourceType === "MedicationStatement").map((entry) => ({
1346
- resource: entry.resource,
1347
- extension: templateUtilities.narrativeLinkExtension(entry.resource)
1572
+ return resources.filter((entry) => entry.resourceType === "MedicationStatement").map((entry) => ({
1573
+ resource: entry,
1574
+ extension: templateUtilities.narrativeLinkExtension(entry)
1348
1575
  }));
1349
1576
  }
1350
1577
  /**
@@ -1440,28 +1667,26 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1440
1667
  var ImmunizationsTemplate = class _ImmunizationsTemplate {
1441
1668
  /**
1442
1669
  * Generate HTML narrative for Immunization resources
1443
- * @param resource - FHIR Bundle containing Immunization resources
1670
+ * @param resources - FHIR resources array containing Immunization resources
1444
1671
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1445
1672
  * @returns HTML string for rendering
1446
1673
  */
1447
- generateNarrative(resource, timezone) {
1448
- if (resource.entry && Array.isArray(resource.entry)) {
1449
- resource.entry.sort((a, b) => {
1450
- const dateA = a.resource?.occurrenceDateTime;
1451
- const dateB = b.resource?.occurrenceDateTime;
1452
- return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
1453
- });
1454
- }
1455
- return _ImmunizationsTemplate.generateStaticNarrative(resource, timezone);
1674
+ generateNarrative(resources, timezone) {
1675
+ resources.sort((a, b) => {
1676
+ const dateA = a.occurrenceDateTime;
1677
+ const dateB = b.occurrenceDateTime;
1678
+ return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
1679
+ });
1680
+ return _ImmunizationsTemplate.generateStaticNarrative(resources, timezone);
1456
1681
  }
1457
1682
  /**
1458
1683
  * Internal static implementation that actually generates the narrative
1459
- * @param resource - FHIR Bundle containing Immunization resources
1684
+ * @param resources - FHIR resources array containing Immunization resources
1460
1685
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1461
1686
  * @returns HTML string for rendering
1462
1687
  */
1463
- static generateStaticNarrative(resource, timezone) {
1464
- const templateUtilities = new TemplateUtilities(resource);
1688
+ static generateStaticNarrative(resources, timezone) {
1689
+ const templateUtilities = new TemplateUtilities(resources);
1465
1690
  let html = `
1466
1691
  <table>
1467
1692
  <thead>
@@ -1476,21 +1701,20 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1476
1701
  </tr>
1477
1702
  </thead>
1478
1703
  <tbody>`;
1479
- if (resource.entry && Array.isArray(resource.entry)) {
1480
- for (const entry of resource.entry) {
1481
- if (entry.resource?.resourceType === "Immunization") {
1482
- const imm = entry.resource;
1483
- html += `
1484
- <tr id="${templateUtilities.narrativeLinkId(imm)}">
1485
- <td>${templateUtilities.codeableConcept(imm.vaccineCode)}</td>
1486
- <td>${imm.status || ""}</td>
1487
- <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
1488
- <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
1489
- <td>${imm.lotNumber || ""}</td>
1490
- <td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
1491
- <td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
1492
- </tr>`;
1493
- }
1704
+ const immunizations = resources.filter((resourceItem) => resourceItem.resourceType === "Immunization");
1705
+ if (immunizations.length > 0) {
1706
+ for (const resourceItem of immunizations) {
1707
+ const imm = resourceItem;
1708
+ html += `
1709
+ <tr id="${templateUtilities.narrativeLinkId(imm)}">
1710
+ <td>${templateUtilities.codeableConcept(imm.vaccineCode)}</td>
1711
+ <td>${imm.status || ""}</td>
1712
+ <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
1713
+ <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
1714
+ <td>${imm.lotNumber || ""}</td>
1715
+ <td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
1716
+ <td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
1717
+ </tr>`;
1494
1718
  }
1495
1719
  }
1496
1720
  html += `
@@ -1504,23 +1728,23 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1504
1728
  var ProblemListTemplate = class _ProblemListTemplate {
1505
1729
  /**
1506
1730
  * Generate HTML narrative for Problem List
1507
- * @param resource - FHIR Bundle containing Condition resources
1731
+ * @param resources - FHIR Condition resources
1508
1732
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1509
1733
  * @returns HTML string for rendering
1510
1734
  */
1511
- generateNarrative(resource, timezone) {
1512
- return _ProblemListTemplate.generateStaticNarrative(resource, timezone);
1735
+ generateNarrative(resources, timezone) {
1736
+ return _ProblemListTemplate.generateStaticNarrative(resources, timezone);
1513
1737
  }
1514
1738
  /**
1515
1739
  * Internal static implementation that actually generates the narrative
1516
- * @param resource - FHIR Bundle containing Condition resources
1740
+ * @param resources - FHIR Condition resources
1517
1741
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1518
1742
  * @returns HTML string for rendering
1519
1743
  */
1520
- static generateStaticNarrative(resource, timezone) {
1521
- const templateUtilities = new TemplateUtilities(resource);
1744
+ static generateStaticNarrative(resources, timezone) {
1745
+ const templateUtilities = new TemplateUtilities(resources);
1522
1746
  let html = ``;
1523
- const activeConditions = resource.entry?.map((entry) => entry.resource) || [];
1747
+ const activeConditions = resources.map((entry) => entry) || [];
1524
1748
  activeConditions.sort((a, b) => {
1525
1749
  const dateA = a.onsetDateTime ? new Date(a.onsetDateTime).getTime() : 0;
1526
1750
  const dateB = b.onsetDateTime ? new Date(b.onsetDateTime).getTime() : 0;
@@ -1551,26 +1775,95 @@ var ProblemListTemplate = class _ProblemListTemplate {
1551
1775
  }
1552
1776
  };
1553
1777
 
1778
+ // src/structures/ips_section_constants.ts
1779
+ var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
1780
+ "Systolic Blood Pressure": "valueRatio.numerator.value",
1781
+ "Diastolic Blood Pressure": "valueRatio.denominator.value",
1782
+ "Default": "valueString"
1783
+ };
1784
+
1554
1785
  // src/narratives/templates/typescript/VitalSignsTemplate.ts
1555
1786
  var VitalSignsTemplate = class _VitalSignsTemplate {
1556
1787
  /**
1557
1788
  * Generate HTML narrative for Vital Signs
1558
- * @param resource - FHIR Bundle containing Observation resources
1789
+ * @param resources - FHIR Observation resources
1559
1790
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1560
1791
  * @returns HTML string for rendering
1561
1792
  */
1562
- generateNarrative(resource, timezone) {
1563
- return _VitalSignsTemplate.generateStaticNarrative(resource, timezone);
1793
+ generateNarrative(resources, timezone) {
1794
+ return _VitalSignsTemplate.generateStaticNarrative(resources, timezone);
1795
+ }
1796
+ /**
1797
+ * Generate HTML narrative for vital signs using summary
1798
+ * @param resources - FHIR Composition resources
1799
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1800
+ * @returns HTML string for rendering
1801
+ */
1802
+ generateSummaryNarrative(resources, timezone) {
1803
+ const templateUtilities = new TemplateUtilities(resources);
1804
+ let html = `
1805
+ <div>
1806
+ <table>
1807
+ <thead>
1808
+ <tr>
1809
+ <th>Vital Name</th>
1810
+ <th>Result</th>
1811
+ <th>Reference Range</th>
1812
+ <th>Date</th>
1813
+ </tr>
1814
+ </thead>
1815
+ <tbody>`;
1816
+ for (const resourceItem of resources) {
1817
+ for (const rowData of resourceItem.section ?? []) {
1818
+ const data = {};
1819
+ for (const columnData of rowData.section ?? []) {
1820
+ const columnTitle = columnData.title;
1821
+ if (columnTitle) {
1822
+ if (Object.keys(VITAL_SIGNS_SUMMARY_COMPONENT_MAP).includes(
1823
+ columnTitle
1824
+ )) {
1825
+ const vitalData = {};
1826
+ for (const component of columnData.section?.[0]?.section ?? []) {
1827
+ if (component.title) {
1828
+ vitalData[component.title] = component.text?.div ?? "";
1829
+ }
1830
+ }
1831
+ const vitalValue = templateUtilities.extractObservationSummaryValue(
1832
+ vitalData,
1833
+ timezone
1834
+ );
1835
+ if (vitalValue) {
1836
+ const dataKey = VITAL_SIGNS_SUMMARY_COMPONENT_MAP[columnTitle] ?? VITAL_SIGNS_SUMMARY_COMPONENT_MAP["Default"];
1837
+ data[dataKey] = vitalValue;
1838
+ }
1839
+ }
1840
+ data[columnTitle] = columnData.text?.div ?? "";
1841
+ }
1842
+ }
1843
+ html += `
1844
+ <tr>
1845
+ <td>${data["Vital Name"] ?? "-"}</td>
1846
+ <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? "-"}</td>
1847
+ <td>${templateUtilities.extractObservationSummaryReferenceRange(data) ?? "-"}</td>
1848
+ <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? "-"}</td>
1849
+ </tr>`;
1850
+ }
1851
+ }
1852
+ html += `
1853
+ </tbody>
1854
+ </table>
1855
+ </div>`;
1856
+ return html;
1564
1857
  }
1565
1858
  /**
1566
1859
  * Internal static implementation that actually generates the narrative
1567
- * @param resource - FHIR Bundle containing Observation resources
1860
+ * @param resources - FHIR Observation resources
1568
1861
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1569
1862
  * @returns HTML string for rendering
1570
1863
  */
1571
- static generateStaticNarrative(resource, timezone) {
1572
- const templateUtilities = new TemplateUtilities(resource);
1573
- const observations = resource.entry?.map((entry) => entry.resource) || [];
1864
+ static generateStaticNarrative(resources, timezone) {
1865
+ const templateUtilities = new TemplateUtilities(resources);
1866
+ const observations = resources.map((entry) => entry) || [];
1574
1867
  observations.sort((a, b) => {
1575
1868
  const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
1576
1869
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
@@ -1580,7 +1873,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1580
1873
  <table>
1581
1874
  <thead>
1582
1875
  <tr>
1583
- <th>Code</th>
1876
+ <th>Vital Name</th>
1584
1877
  <th>Result</th>
1585
1878
  <th>Unit</th>
1586
1879
  <th>Interpretation</th>
@@ -1613,28 +1906,21 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1613
1906
  var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
1614
1907
  /**
1615
1908
  * Generate HTML narrative for Medical Device resources
1616
- * @param resource - FHIR Bundle containing Device resources
1909
+ * @param resources - FHIR resources array containing Device resources
1617
1910
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1618
1911
  * @returns HTML string for rendering
1619
1912
  */
1620
- generateNarrative(resource, timezone) {
1621
- if (resource.entry && Array.isArray(resource.entry)) {
1622
- resource.entry.sort((a, b) => {
1623
- const dateA = a.resource?.recordedOn;
1624
- const dateB = b.resource?.recordedOn;
1625
- return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
1626
- });
1627
- }
1628
- return _MedicalDevicesTemplate.generateStaticNarrative(resource, timezone);
1913
+ generateNarrative(resources, timezone) {
1914
+ return _MedicalDevicesTemplate.generateStaticNarrative(resources, timezone);
1629
1915
  }
1630
1916
  /**
1631
1917
  * Internal static implementation that actually generates the narrative
1632
- * @param resource - FHIR Bundle containing Device resources
1918
+ * @param resources - FHIR resources array containing Device resources
1633
1919
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1634
1920
  * @returns HTML string for rendering
1635
1921
  */
1636
- static generateStaticNarrative(resource, timezone) {
1637
- const templateUtilities = new TemplateUtilities(resource);
1922
+ static generateStaticNarrative(resources, timezone) {
1923
+ const templateUtilities = new TemplateUtilities(resources);
1638
1924
  let html = `
1639
1925
  <table>
1640
1926
  <thead>
@@ -1646,19 +1932,19 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
1646
1932
  </tr>
1647
1933
  </thead>
1648
1934
  <tbody>`;
1649
- if (resource.entry && Array.isArray(resource.entry)) {
1650
- for (const entry of resource.entry) {
1651
- if (entry.resource?.resourceType === "DeviceUseStatement") {
1652
- const dus = entry.resource;
1653
- html += `
1654
- <tr id="${templateUtilities.narrativeLinkId(dus)}">
1655
- <td>${templateUtilities.renderDevice(dus.device)}</td>
1656
- <td>${dus.status || ""}</td>
1657
- <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
1658
- <td>${templateUtilities.renderRecorded(dus.recordedOn, timezone)}</td>
1659
- </tr>`;
1660
- }
1661
- }
1935
+ const deviceStatements = resources.filter((resourceItem) => resourceItem.resourceType === "DeviceUseStatement").map((resourceItem) => resourceItem).sort((a, b) => {
1936
+ const dateA = a.recordedOn;
1937
+ const dateB = b.recordedOn;
1938
+ return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
1939
+ });
1940
+ for (const dus of deviceStatements) {
1941
+ html += `
1942
+ <tr id="${templateUtilities.narrativeLinkId(dus)}">
1943
+ <td>${templateUtilities.renderDevice(dus.device)}</td>
1944
+ <td>${dus.status || ""}</td>
1945
+ <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
1946
+ <td>${templateUtilities.renderRecorded(dus.recordedOn, timezone)}</td>
1947
+ </tr>`;
1662
1948
  }
1663
1949
  html += `
1664
1950
  </tbody>
@@ -1671,23 +1957,23 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
1671
1957
  var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
1672
1958
  /**
1673
1959
  * Generate HTML narrative for Diagnostic Results
1674
- * @param resource - FHIR Bundle containing Observation and DiagnosticReport resources
1960
+ * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
1675
1961
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1676
1962
  * @returns HTML string for rendering
1677
1963
  */
1678
- generateNarrative(resource, timezone) {
1679
- return _DiagnosticResultsTemplate.generateStaticNarrative(resource, timezone);
1964
+ generateNarrative(resources, timezone) {
1965
+ return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
1680
1966
  }
1681
1967
  /**
1682
1968
  * Internal static implementation that actually generates the narrative
1683
- * @param resource - FHIR Bundle containing Observation and DiagnosticReport resources
1969
+ * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
1684
1970
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1685
1971
  * @returns HTML string for rendering
1686
1972
  */
1687
- static generateStaticNarrative(resource, timezone) {
1688
- const templateUtilities = new TemplateUtilities(resource);
1973
+ static generateStaticNarrative(resources, timezone) {
1974
+ const templateUtilities = new TemplateUtilities(resources);
1689
1975
  let html = "";
1690
- const observations = this.getObservations(resource);
1976
+ const observations = this.getObservations(resources);
1691
1977
  if (observations.length > 0) {
1692
1978
  observations.sort((a, b) => {
1693
1979
  const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
@@ -1696,7 +1982,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
1696
1982
  });
1697
1983
  html += this.renderObservations(templateUtilities, observations, timezone);
1698
1984
  }
1699
- const diagnosticReports = this.getDiagnosticReports(resource);
1985
+ const diagnosticReports = this.getDiagnosticReports(resources);
1700
1986
  if (diagnosticReports.length > 0) {
1701
1987
  diagnosticReports.sort((a, b) => {
1702
1988
  const dateA = a.issued;
@@ -1708,26 +1994,20 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
1708
1994
  return html;
1709
1995
  }
1710
1996
  /**
1711
- * Extract Observation resources from the bundle
1712
- * @param resource - FHIR Bundle
1713
- * @returns Array of Observation resources
1714
- */
1715
- static getObservations(resource) {
1716
- if (!resource.entry || !Array.isArray(resource.entry)) {
1717
- return [];
1718
- }
1719
- return resource.entry.filter((entry) => entry.resource?.resourceType === "Observation").map((entry) => entry.resource);
1997
+ * Get all Observation resources from the resource array
1998
+ * @param resources - FHIR resources array
1999
+ * @returns Array of Observation resources
2000
+ */
2001
+ static getObservations(resources) {
2002
+ return resources.filter((resourceItem) => resourceItem.resourceType === "Observation").map((resourceItem) => resourceItem);
1720
2003
  }
1721
2004
  /**
1722
- * Extract DiagnosticReport resources from the bundle
1723
- * @param resource - FHIR Bundle
2005
+ * Get all DiagnosticReport resources from the resource array
2006
+ * @param resources - FHIR resources array
1724
2007
  * @returns Array of DiagnosticReport resources
1725
2008
  */
1726
- static getDiagnosticReports(resource) {
1727
- if (!resource.entry || !Array.isArray(resource.entry)) {
1728
- return [];
1729
- }
1730
- return resource.entry.filter((entry) => entry.resource?.resourceType === "DiagnosticReport").map((entry) => entry.resource);
2009
+ static getDiagnosticReports(resources) {
2010
+ return resources.filter((resourceItem) => resourceItem.resourceType === "DiagnosticReport").map((resourceItem) => resourceItem);
1731
2011
  }
1732
2012
  /**
1733
2013
  * Render HTML table for Observation resources
@@ -1813,26 +2093,26 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
1813
2093
  var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
1814
2094
  /**
1815
2095
  * Generate HTML narrative for Procedure resources
1816
- * @param resource - FHIR Bundle containing Procedure resources
2096
+ * @param resources - FHIR Procedure resources
1817
2097
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1818
2098
  * @returns HTML string for rendering
1819
2099
  */
1820
- generateNarrative(resource, timezone) {
1821
- resource.entry?.sort((a, b) => {
1822
- const dateA = a.resource.performedDateTime || a.resource.performedPeriod?.start;
1823
- const dateB = b.resource.performedDateTime || b.resource.performedPeriod?.start;
2100
+ generateNarrative(resources, timezone) {
2101
+ resources.sort((a, b) => {
2102
+ const dateA = a.performedDateTime || a.performedPeriod?.start;
2103
+ const dateB = b.performedDateTime || b.performedPeriod?.start;
1824
2104
  return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
1825
2105
  });
1826
- return _HistoryOfProceduresTemplate.generateStaticNarrative(resource, timezone);
2106
+ return _HistoryOfProceduresTemplate.generateStaticNarrative(resources, timezone);
1827
2107
  }
1828
2108
  /**
1829
2109
  * Internal static implementation that actually generates the narrative
1830
- * @param resource - FHIR Bundle containing Procedure resources
2110
+ * @param resources - FHIR Procedure resources
1831
2111
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1832
2112
  * @returns HTML string for rendering
1833
2113
  */
1834
- static generateStaticNarrative(resource, timezone) {
1835
- const templateUtilities = new TemplateUtilities(resource);
2114
+ static generateStaticNarrative(resources, timezone) {
2115
+ const templateUtilities = new TemplateUtilities(resources);
1836
2116
  let html = `
1837
2117
  <table>
1838
2118
  <thead>
@@ -1843,16 +2123,14 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
1843
2123
  </tr>
1844
2124
  </thead>
1845
2125
  <tbody>`;
1846
- if (resource.entry && Array.isArray(resource.entry)) {
1847
- for (const entry of resource.entry) {
1848
- const proc = entry.resource;
1849
- html += `
1850
- <tr id="${templateUtilities.narrativeLinkId(proc)}">
1851
- <td>${templateUtilities.codeableConcept(proc.code, "display")}</td>
1852
- <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
1853
- <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
1854
- </tr>`;
1855
- }
2126
+ for (const resourceItem of resources) {
2127
+ const proc = resourceItem;
2128
+ html += `
2129
+ <tr id="${templateUtilities.narrativeLinkId(proc)}">
2130
+ <td>${templateUtilities.codeableConcept(proc.code, "display")}</td>
2131
+ <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
2132
+ <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
2133
+ </tr>`;
1856
2134
  }
1857
2135
  html += `
1858
2136
  </tbody>
@@ -1865,22 +2143,22 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
1865
2143
  var SocialHistoryTemplate = class _SocialHistoryTemplate {
1866
2144
  /**
1867
2145
  * Generate HTML narrative for Social History
1868
- * @param resource - FHIR Bundle containing Observation resources
2146
+ * @param resources - FHIR Observation resources
1869
2147
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1870
2148
  * @returns HTML string for rendering
1871
2149
  */
1872
- generateNarrative(resource, timezone) {
1873
- return _SocialHistoryTemplate.generateStaticNarrative(resource, timezone);
2150
+ generateNarrative(resources, timezone) {
2151
+ return _SocialHistoryTemplate.generateStaticNarrative(resources, timezone);
1874
2152
  }
1875
2153
  /**
1876
2154
  * Internal static implementation that actually generates the narrative
1877
- * @param resource - FHIR Bundle containing Observation resources
2155
+ * @param resources - FHIR Observation resources
1878
2156
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1879
2157
  * @returns HTML string for rendering
1880
2158
  */
1881
- static generateStaticNarrative(resource, timezone) {
1882
- const templateUtilities = new TemplateUtilities(resource);
1883
- const observations = resource.entry?.map((entry) => entry.resource) || [];
2159
+ static generateStaticNarrative(resources, timezone) {
2160
+ const templateUtilities = new TemplateUtilities(resources);
2161
+ const observations = resources.map((entry) => entry) || [];
1884
2162
  observations.sort((a, b) => {
1885
2163
  const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
1886
2164
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
@@ -1919,14 +2197,14 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
1919
2197
  var PastHistoryOfIllnessTemplate = class {
1920
2198
  /**
1921
2199
  * Generate HTML narrative for Past History of Illnesses
1922
- * @param resource - FHIR Bundle containing Condition resources
2200
+ * @param resources - FHIR Condition resources
1923
2201
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1924
2202
  * @returns HTML string for rendering
1925
2203
  */
1926
- generateNarrative(resource, timezone) {
1927
- const templateUtilities = new TemplateUtilities(resource);
2204
+ generateNarrative(resources, timezone) {
2205
+ const templateUtilities = new TemplateUtilities(resources);
1928
2206
  let html = ``;
1929
- const resolvedConditions = resource.entry?.map((entry) => entry.resource) || [];
2207
+ const resolvedConditions = resources.map((entry) => entry) || [];
1930
2208
  resolvedConditions.sort((a, b) => {
1931
2209
  const dateA = a.onsetDateTime ? new Date(a.onsetDateTime).getTime() : 0;
1932
2210
  const dateB = b.onsetDateTime ? new Date(b.onsetDateTime).getTime() : 0;
@@ -1963,13 +2241,13 @@ var PastHistoryOfIllnessTemplate = class {
1963
2241
  var PlanOfCareTemplate = class {
1964
2242
  /**
1965
2243
  * Generate HTML narrative for Plan of Care
1966
- * @param resource - FHIR Bundle containing CarePlan resources
2244
+ * @param resources - FHIR CarePlan resources
1967
2245
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1968
2246
  * @returns HTML string for rendering
1969
2247
  */
1970
- generateNarrative(resource, timezone) {
1971
- const templateUtilities = new TemplateUtilities(resource);
1972
- const carePlans = resource.entry?.map((entry) => entry.resource) || [];
2248
+ generateNarrative(resources, timezone) {
2249
+ const templateUtilities = new TemplateUtilities(resources);
2250
+ const carePlans = resources.map((entry) => entry) || [];
1973
2251
  carePlans.sort((a, b) => {
1974
2252
  const endA = a.period?.end ? new Date(a.period?.end).getTime() : 0;
1975
2253
  const endB = b.period?.end ? new Date(b.period?.end).getTime() : 0;
@@ -2008,37 +2286,35 @@ var PlanOfCareTemplate = class {
2008
2286
  var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2009
2287
  /**
2010
2288
  * Generate HTML narrative for Functional Status
2011
- * @param resource - FHIR Bundle containing Observation resources
2289
+ * @param resources - FHIR resources array containing Observation resources
2012
2290
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2013
2291
  * @returns HTML string for rendering
2014
2292
  */
2015
- generateNarrative(resource, timezone) {
2016
- return _FunctionalStatusTemplate.generateStaticNarrative(resource, timezone);
2293
+ generateNarrative(resources, timezone) {
2294
+ return _FunctionalStatusTemplate.generateStaticNarrative(resources, timezone);
2017
2295
  }
2018
2296
  /**
2019
2297
  * Internal static implementation that actually generates the narrative
2020
- * @param resource - FHIR Bundle containing Observation resources
2298
+ * @param resources - FHIR resources array containing Observation resources
2021
2299
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2022
2300
  * @returns HTML string for rendering
2023
2301
  */
2024
- static generateStaticNarrative(resource, timezone) {
2025
- const templateUtilities = new TemplateUtilities(resource);
2302
+ static generateStaticNarrative(resources, timezone) {
2303
+ const templateUtilities = new TemplateUtilities(resources);
2026
2304
  let html = ``;
2027
2305
  const activeConditions = [];
2028
2306
  const clinicalImpressions = [];
2029
- if (resource.entry && Array.isArray(resource.entry)) {
2030
- for (const entry of resource.entry) {
2031
- if (entry.resource?.resourceType === "Condition") {
2032
- const cond = entry.resource;
2033
- const isResolved = cond.clinicalStatus?.coding?.some(
2034
- (c) => c.code === "resolved" || c.code === "inactive" || c.display?.toLowerCase().includes("resolved")
2035
- );
2036
- if (!isResolved) {
2037
- activeConditions.push(cond);
2038
- }
2039
- } else if (entry.resource?.resourceType === "ClinicalImpression") {
2040
- clinicalImpressions.push(entry.resource);
2307
+ for (const resourceItem of resources) {
2308
+ if (resourceItem.resourceType === "Condition") {
2309
+ const cond = resourceItem;
2310
+ const isResolved = cond.clinicalStatus?.coding?.some(
2311
+ (c) => c.code === "resolved" || c.code === "inactive" || c.display?.toLowerCase().includes("resolved")
2312
+ );
2313
+ if (!isResolved) {
2314
+ activeConditions.push(cond);
2041
2315
  }
2316
+ } else if (resourceItem.resourceType === "ClinicalImpression") {
2317
+ clinicalImpressions.push(resourceItem);
2042
2318
  }
2043
2319
  }
2044
2320
  activeConditions.sort((a, b) => {
@@ -2135,22 +2411,22 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2135
2411
  var PregnancyTemplate = class _PregnancyTemplate {
2136
2412
  /**
2137
2413
  * Generate HTML narrative for Pregnancy
2138
- * @param resource - FHIR Bundle containing Observation resources
2414
+ * @param resources - FHIR Observation resources
2139
2415
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2140
2416
  * @returns HTML string for rendering
2141
2417
  */
2142
- generateNarrative(resource, timezone) {
2143
- return _PregnancyTemplate.generateStaticNarrative(resource, timezone);
2418
+ generateNarrative(resources, timezone) {
2419
+ return _PregnancyTemplate.generateStaticNarrative(resources, timezone);
2144
2420
  }
2145
2421
  /**
2146
2422
  * Internal static implementation that actually generates the narrative
2147
- * @param resource - FHIR Bundle containing Observation resources
2423
+ * @param resources - FHIR Observation resources
2148
2424
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2149
2425
  * @returns HTML string for rendering
2150
2426
  */
2151
- static generateStaticNarrative(resource, timezone) {
2152
- const templateUtilities = new TemplateUtilities(resource);
2153
- const observations = resource.entry?.map((entry) => entry.resource) || [];
2427
+ static generateStaticNarrative(resources, timezone) {
2428
+ const templateUtilities = new TemplateUtilities(resources);
2429
+ const observations = resources.map((entry) => entry) || [];
2154
2430
  observations.sort((a, b) => {
2155
2431
  const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
2156
2432
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
@@ -2166,8 +2442,8 @@ var PregnancyTemplate = class _PregnancyTemplate {
2166
2442
  </tr>
2167
2443
  </thead>
2168
2444
  <tbody>`;
2169
- for (const resource2 of observations) {
2170
- const obs = resource2;
2445
+ for (const resource of observations) {
2446
+ const obs = resource;
2171
2447
  html += `
2172
2448
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2173
2449
  <td>${templateUtilities.extractPregnancyStatus(obs)}</td>
@@ -2186,29 +2462,27 @@ var PregnancyTemplate = class _PregnancyTemplate {
2186
2462
  var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
2187
2463
  /**
2188
2464
  * Generate HTML narrative for Advance Directives
2189
- * @param resource - FHIR Bundle containing Advance Directive resources
2465
+ * @param resources - FHIR Consent resources
2190
2466
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2191
2467
  * @returns HTML string for rendering
2192
2468
  */
2193
- generateNarrative(resource, timezone) {
2194
- if (resource.entry && Array.isArray(resource.entry)) {
2195
- resource.entry.sort((a, b) => {
2196
- const dateA = new Date(a.resource.dateTime || 0);
2197
- const dateB = new Date(b.resource.dateTime || 0);
2198
- return dateB.getTime() - dateA.getTime();
2199
- });
2200
- }
2201
- return _AdvanceDirectivesTemplate.generateStaticNarrative(resource, timezone);
2469
+ generateNarrative(resources, timezone) {
2470
+ resources.sort((a, b) => {
2471
+ const dateA = new Date(a.dateTime || 0);
2472
+ const dateB = new Date(b.dateTime || 0);
2473
+ return dateB.getTime() - dateA.getTime();
2474
+ });
2475
+ return _AdvanceDirectivesTemplate.generateStaticNarrative(resources, timezone);
2202
2476
  }
2203
2477
  /**
2204
2478
  * Internal static implementation that actually generates the narrative
2205
- * @param resource - FHIR Bundle containing Advance Directive resources
2479
+ * @param resources - FHIR Consent resources
2206
2480
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2207
2481
  * @returns HTML string for rendering
2208
2482
  */
2209
2483
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
2210
- static generateStaticNarrative(resource, timezone) {
2211
- const templateUtilities = new TemplateUtilities(resource);
2484
+ static generateStaticNarrative(resources, timezone) {
2485
+ const templateUtilities = new TemplateUtilities(resources);
2212
2486
  let html = `
2213
2487
  <table>
2214
2488
  <thead>
@@ -2220,17 +2494,15 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
2220
2494
  </tr>
2221
2495
  </thead>
2222
2496
  <tbody>`;
2223
- if (resource.entry && Array.isArray(resource.entry)) {
2224
- for (const entry of resource.entry) {
2225
- const consent = entry.resource;
2226
- html += `
2227
- <tr id="${templateUtilities.narrativeLinkId(consent)}">
2228
- <td>${templateUtilities.codeableConcept(consent.scope, "display")}</td>
2229
- <td>${consent.status || ""}</td>
2230
- <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
2231
- <td>${consent.dateTime || ""}</td>
2232
- </tr>`;
2233
- }
2497
+ for (const resourceItem of resources) {
2498
+ const consent = resourceItem;
2499
+ html += `
2500
+ <tr id="${templateUtilities.narrativeLinkId(consent)}">
2501
+ <td>${templateUtilities.codeableConcept(consent.scope, "display")}</td>
2502
+ <td>${consent.status || ""}</td>
2503
+ <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
2504
+ <td>${consent.dateTime || ""}</td>
2505
+ </tr>`;
2234
2506
  }
2235
2507
  html += `
2236
2508
  </tbody>
@@ -2244,16 +2516,20 @@ var TypeScriptTemplateMapper = class {
2244
2516
  /**
2245
2517
  * Generates HTML narrative for a specific IPS section
2246
2518
  * @param section - The IPS section
2247
- * @param resource - FHIR Bundle containing resources
2519
+ * @param resources - FHIR resources
2248
2520
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2521
+ * @param useSectionSummary - Whether to use the section summary for narrative generation
2249
2522
  * @returns HTML string for rendering
2250
2523
  */
2251
- static generateNarrative(section, resource, timezone) {
2524
+ static generateNarrative(section, resources, timezone, useSectionSummary = false) {
2252
2525
  const templateClass = this.sectionToTemplate[section];
2253
2526
  if (!templateClass) {
2254
2527
  throw new Error(`No template found for section: ${section}`);
2255
2528
  }
2256
- return templateClass.generateNarrative(resource, timezone);
2529
+ return useSectionSummary ? templateClass.generateSummaryNarrative(
2530
+ resources,
2531
+ timezone
2532
+ ) : templateClass.generateNarrative(resources, timezone);
2257
2533
  }
2258
2534
  };
2259
2535
  // Map of section types to their template classes
@@ -2309,21 +2585,15 @@ var NarrativeGenerator = class {
2309
2585
  * @param section - IPS section type
2310
2586
  * @param resources - Array of domain resources
2311
2587
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2588
+ * @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
2312
2589
  * @returns Generated HTML content or undefined if no resources
2313
2590
  */
2314
- static async generateNarrativeContentAsync(section, resources, timezone) {
2591
+ static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false) {
2315
2592
  if (!resources || resources.length === 0) {
2316
2593
  return void 0;
2317
2594
  }
2318
2595
  try {
2319
- const bundle = {
2320
- resourceType: "Bundle",
2321
- type: "collection",
2322
- entry: resources.map((resource) => ({
2323
- resource
2324
- }))
2325
- };
2326
- const content = TypeScriptTemplateMapper.generateNarrative(section, bundle, timezone);
2596
+ const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary);
2327
2597
  if (!content) {
2328
2598
  return void 0;
2329
2599
  }
@@ -2374,10 +2644,11 @@ var NarrativeGenerator = class {
2374
2644
  * @param resources - Array of domain resources
2375
2645
  * @param timezone - Optional timezone to use for date formatting
2376
2646
  * @param minify - Whether to minify the HTML content (default: true)
2647
+ * @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
2377
2648
  * @returns Promise that resolves to a FHIR Narrative object or undefined if no resources
2378
2649
  */
2379
- static async generateNarrativeAsync(section, resources, timezone, minify = true) {
2380
- const content = await this.generateNarrativeContentAsync(section, resources, timezone);
2650
+ static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false) {
2651
+ const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary);
2381
2652
  if (!content) {
2382
2653
  return void 0;
2383
2654
  }
@@ -2408,22 +2679,51 @@ var ComprehensiveIPSCompositionBuilder = class {
2408
2679
  /**
2409
2680
  * sets the patient resource for the IPS Composition.
2410
2681
  * This is not needed if you are calling read_bundle, but can be used to set the patient resource directly.
2411
- * @param patient - FHIR Patient resource to set
2682
+ * @param patients - FHIR Patient resource to set
2412
2683
  */
2413
- setPatient(patient) {
2414
- if (!patient || patient.resourceType !== "Patient") {
2684
+ setPatient(patients) {
2685
+ if (!Array.isArray(patients)) {
2686
+ patients = [patients];
2687
+ }
2688
+ if (patients.length === 0 || !patients.every((patient) => patient.resourceType === "Patient")) {
2415
2689
  throw new Error("Invalid Patient resource");
2416
2690
  }
2417
- this.patient = patient;
2691
+ this.patients = patients;
2418
2692
  return this;
2419
2693
  }
2420
2694
  /**
2421
2695
  * Adds a section to the composition with async HTML minification
2696
+ * @param narrative - Narrative content for the section
2697
+ * @param sectionType - IPS section type
2698
+ * @param validResources - Array of domain resources
2699
+ */
2700
+ addSectionAsync(narrative, sectionType, validResources) {
2701
+ const sectionEntry = {
2702
+ title: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType,
2703
+ code: {
2704
+ coding: [{
2705
+ system: "http://loinc.org",
2706
+ code: IPS_SECTION_LOINC_CODES[sectionType],
2707
+ display: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType
2708
+ }],
2709
+ text: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType
2710
+ },
2711
+ text: narrative,
2712
+ entry: validResources.map((resource) => ({
2713
+ reference: `${resource.resourceType}/${resource.id}`,
2714
+ display: resource.resourceType
2715
+ }))
2716
+ };
2717
+ this.sections.push(sectionEntry);
2718
+ return this;
2719
+ }
2720
+ /**
2721
+ * Make and adds a section to the composition with async HTML minification
2422
2722
  * @param sectionType - IPS section type
2423
2723
  * @param validResources - Array of domain resources
2424
2724
  * @param timezone - Optional timezone to use for date formatting
2425
2725
  */
2426
- async addSectionAsync(sectionType, validResources, timezone) {
2726
+ async makeSectionAsync(sectionType, validResources, timezone) {
2427
2727
  for (const resource of validResources) {
2428
2728
  this.resources.add(resource);
2429
2729
  }
@@ -2443,51 +2743,68 @@ var ComprehensiveIPSCompositionBuilder = class {
2443
2743
  } else {
2444
2744
  return this;
2445
2745
  }
2446
- const sectionEntry = {
2447
- title: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType,
2448
- code: {
2449
- coding: [{
2450
- system: "http://loinc.org",
2451
- code: IPS_SECTION_LOINC_CODES[sectionType],
2452
- display: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType
2453
- }],
2454
- text: IPS_SECTION_DISPLAY_NAMES[sectionType] || sectionType
2455
- },
2456
- text: narrative,
2457
- entry: validResources.map((resource) => ({
2458
- reference: `${resource.resourceType}/${resource.id}`,
2459
- display: resource.resourceType
2460
- }))
2461
- };
2462
- this.sections.push(sectionEntry);
2746
+ this.addSectionAsync(narrative, sectionType, validResources);
2463
2747
  }
2464
2748
  return this;
2465
2749
  }
2750
+ async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone) {
2751
+ const sectionResources = [];
2752
+ for (const summaryComposition of summaryCompositions) {
2753
+ const resourceEntries = summaryComposition?.section?.flatMap((sec) => sec.entry || []) ?? [];
2754
+ resources.forEach((resource) => {
2755
+ if (resourceEntries?.some((entry) => entry.reference === `${resource.resourceType}/${resource.id}`)) {
2756
+ this.resources.add(resource);
2757
+ sectionResources.push(resource);
2758
+ }
2759
+ });
2760
+ }
2761
+ const narrative = await NarrativeGenerator.generateNarrativeAsync(
2762
+ sectionType,
2763
+ summaryCompositions,
2764
+ timezone,
2765
+ true,
2766
+ true
2767
+ );
2768
+ this.addSectionAsync(narrative, sectionType, sectionResources);
2769
+ return this;
2770
+ }
2466
2771
  /**
2467
2772
  * Reads a FHIR Bundle and extracts resources for each section defined in IPSSections.
2468
2773
  * @param bundle - FHIR Bundle containing resources
2469
2774
  * @param timezone - Optional timezone to use for date formatting
2775
+ * @param useSummaryCompositions - Whether to use summary compositions (default: false)
2470
2776
  */
2471
- async readBundleAsync(bundle, timezone) {
2777
+ async readBundleAsync(bundle, timezone, useSummaryCompositions = false) {
2472
2778
  if (!bundle.entry) {
2473
2779
  return this;
2474
2780
  }
2475
- const patientEntry = bundle.entry.find((e) => e.resource?.resourceType === "Patient");
2476
- if (!patientEntry || !patientEntry.resource) {
2781
+ const patientEntries = [];
2782
+ const resources = [];
2783
+ bundle.entry.forEach((e) => {
2784
+ if (e.resource?.resourceType === "Patient") {
2785
+ patientEntries.push(e.resource);
2786
+ this.resources.add(e.resource);
2787
+ } else if (e.resource) {
2788
+ resources.push(e.resource);
2789
+ }
2790
+ });
2791
+ if (patientEntries.length === 0) {
2477
2792
  throw new Error("Patient resource not found in the bundle");
2478
2793
  }
2479
- this.patient = patientEntry.resource;
2480
- const resources = bundle.entry.map((e) => e.resource);
2794
+ this.patients = patientEntries;
2481
2795
  for (const sectionType of Object.values(IPSSections)) {
2482
- const resourceTypesForSection = IPSSectionResourceHelper.getResourceTypesForSection(sectionType);
2483
- const customFilter = IPSSectionResourceHelper.getResourceFilterForSection(sectionType);
2484
- let sectionResources = resources.filter(
2485
- (r) => r && typeof r.resourceType === "string" && resourceTypesForSection.includes(r.resourceType)
2486
- );
2487
- if (customFilter) {
2488
- sectionResources = sectionResources.filter((resource) => resource && customFilter(resource));
2796
+ if (sectionType === "Patient" /* PATIENT */) {
2797
+ continue;
2798
+ }
2799
+ const summaryCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType) : void 0;
2800
+ const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : void 0;
2801
+ if (sectionSummary) {
2802
+ await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone);
2803
+ } else {
2804
+ const sectionFilter = IPSSectionResourceHelper.getResourceFilterForSection(sectionType);
2805
+ const sectionResources = resources.filter((resource) => sectionFilter(resource));
2806
+ await this.makeSectionAsync(sectionType, sectionResources, timezone);
2489
2807
  }
2490
- await this.addSectionAsync(sectionType, sectionResources, timezone);
2491
2808
  }
2492
2809
  return this;
2493
2810
  }
@@ -2497,16 +2814,18 @@ var ComprehensiveIPSCompositionBuilder = class {
2497
2814
  * @param authorOrganizationName - Name of the authoring organization
2498
2815
  * @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
2499
2816
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2817
+ * @param patientId - Optional patient ID to use as primary patient for composition reference
2500
2818
  */
2501
- async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone) {
2819
+ async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId) {
2502
2820
  if (baseUrl.endsWith("/")) {
2503
2821
  baseUrl = baseUrl.slice(0, -1);
2504
2822
  }
2505
- if (!this.patient) {
2823
+ if (!this.patients) {
2506
2824
  throw new Error("Patient resource must be set before building the bundle");
2507
2825
  }
2826
+ const primaryPatientId = patientId ?? this.patients[0].id;
2508
2827
  const composition = {
2509
- id: `Composition-${this.patient.id}`,
2828
+ id: `Composition-${primaryPatientId}`,
2510
2829
  resourceType: "Composition",
2511
2830
  status: "final",
2512
2831
  type: {
@@ -2517,7 +2836,7 @@ var ComprehensiveIPSCompositionBuilder = class {
2517
2836
  }]
2518
2837
  },
2519
2838
  subject: {
2520
- reference: `Patient/${this.patient.id}`
2839
+ reference: `Patient/${primaryPatientId}`
2521
2840
  },
2522
2841
  author: [{
2523
2842
  reference: `Organization/${authorOrganizationId}`,
@@ -2529,7 +2848,7 @@ var ComprehensiveIPSCompositionBuilder = class {
2529
2848
  section: this.sections,
2530
2849
  text: await NarrativeGenerator.generateNarrativeAsync(
2531
2850
  "Patient" /* PATIENT */,
2532
- [this.patient],
2851
+ this.patients,
2533
2852
  timezone,
2534
2853
  true
2535
2854
  )
@@ -2548,9 +2867,11 @@ var ComprehensiveIPSCompositionBuilder = class {
2548
2867
  fullUrl: `${baseUrl}/Composition/${composition.id}`,
2549
2868
  resource: composition
2550
2869
  });
2551
- bundle.entry?.push({
2552
- fullUrl: `${baseUrl}/Patient/${this.patient.id}`,
2553
- resource: this.patient
2870
+ this.patients.forEach((patient) => {
2871
+ bundle.entry?.push({
2872
+ fullUrl: `${baseUrl}/Patient/${patient.id}`,
2873
+ resource: patient
2874
+ });
2554
2875
  });
2555
2876
  this.resources.forEach((resource) => {
2556
2877
  if (resource.resourceType !== "Patient") {